[glib] glib-unix: New API to watch some Unix signals



commit 549d895fa4e9c4b5a35c5d9da4db39ca11dc71cc
Author: Colin Walters <walters verbum org>
Date:   Thu Mar 17 10:11:41 2011 -0400

    glib-unix: New API to watch some Unix signals
    
    This new API allows watching a few select Unix signals;
    looking through the list on my system, I didn't see anything
    else that I think it'd reasonable to watch.
    
    We build on the previous patch to make the child watch helper thread
    that existed on Unix handle these signals in the threaded case.
    In the non-threaded case, they're just global variables.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=644941

 docs/reference/glib/glib-docs.sgml    |    1 +
 docs/reference/glib/glib-sections.txt |    4 +-
 glib/glib-unix.c                      |   88 ++++++++-
 glib/glib-unix.h                      |   22 ++-
 glib/glib.symbols                     |    2 +
 glib/gmain-internal.h                 |   35 +++
 glib/gmain.c                          |  371 +++++++++++++++++++++++++++------
 glib/tests/Makefile.am                |    6 +-
 glib/tests/unix.c                     |   81 +++++++
 gthread/tests/Makefile.am             |    5 +
 10 files changed, 544 insertions(+), 71 deletions(-)
---
diff --git a/docs/reference/glib/glib-docs.sgml b/docs/reference/glib/glib-docs.sgml
index cb864ce..208c49b 100644
--- a/docs/reference/glib/glib-docs.sgml
+++ b/docs/reference/glib/glib-docs.sgml
@@ -96,6 +96,7 @@ synchronize their operation.
       <xi:include href="xml/keyfile.xml" />
       <xi:include href="xml/bookmarkfile.xml" />
       <xi:include href="xml/testing.xml" />
+      <xi:include href="xml/gunix.xml" />
       <xi:include href="xml/windows.xml" />
   </chapter>
 
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 9873393..6bf8593 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1925,10 +1925,12 @@ g_win32_ftruncate
 </SECTION>
 
 <SECTION>
-<TITLE>UNIX Compatibility Functions</TITLE>
+<TITLE>UNIX-specific utilities and integration</TITLE>
 <FILE>gunix</FILE>
 G_UNIX_ERROR
 g_unix_pipe_flags
+g_unix_signal_source_new
+g_unix_signal_add_watch_full
 
 </SECTION>
 
diff --git a/glib/glib-unix.c b/glib/glib-unix.c
index e26419e..cf4748b 100644
--- a/glib/glib-unix.c
+++ b/glib/glib-unix.c
@@ -24,12 +24,14 @@
 #include "config.h"
 
 #include "glib-unix.h"
+#include "gmain-internal.h"
 
 #include <string.h>
 
 /**
  * SECTION:gunix
- * @short_description: UNIX-specific utilities and integration
+ * @title: UNIX-specific utilities and integration
+ * @short_description: pipes, signal handling
  * @include: glib-unix.h
  *
  * Most of GLib is intended to be portable; in constrast, this set of
@@ -78,12 +80,12 @@ g_unix_set_error_from_errno_saved (GError **error,
  * @error: a #GError
  *
  * Similar to the UNIX pipe() call, but on modern systems like Linux
- * uses the pipe2 system call, which atomically creates a pipe with
+ * uses the pipe2() system call, which atomically creates a pipe with
  * the configured flags.  The only supported flag currently is
- * FD_CLOEXEC.  If for example you want to configure O_NONBLOCK, that
+ * %FD_CLOEXEC.  If for example you want to configure %O_NONBLOCK, that
  * must still be done separately with fcntl().
  *
- * <note>This function does *not* take O_CLOEXEC, it takes FD_CLOEXEC as if
+ * <note>This function does *not* take %O_CLOEXEC, it takes %FD_CLOEXEC as if
  * for fcntl(); these are different on Linux/glibc.</note>
  *
  * Returns: %TRUE on success, %FALSE if not (and errno will be set).
@@ -132,3 +134,81 @@ g_unix_pipe_flags (int     *fds,
     }
   return TRUE;
 }
+
+/**
+ * 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_create_watch().
+ * 
+ * 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
+ */
+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
+ */
+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-unix.h b/glib/glib-unix.h
index f2f784d..e724778 100644
--- a/glib/glib-unix.h
+++ b/glib/glib-unix.h
@@ -44,19 +44,23 @@
 
 #include <glib.h>
 
+#ifndef G_OS_UNIX
+#error "This header may only be used on UNIX"
+#endif
+
 /**
  * G_UNIX_ERROR:
  * 
  * Error domain for API in the "g_unix_" namespace.  Note that there
- * is no exported enumeration mapping "errno".  Instead, all functions
- * ensure that "errno" is relevant.  The code for all G_UNIX_ERROR is
- * always 0, and the error message is always generated via
+ * is no exported enumeration mapping %errno.  Instead, all functions
+ * ensure that %errno is relevant.  The code for all #G_UNIX_ERROR is
+ * always %0, and the error message is always generated via
  * g_strerror().
  *
- * It is expected that most code will not look at "errno" from these
+ * It is expected that most code will not look at %errno from these
  * APIs. Important cases where one would want to differentiate between
  * errors are already covered by existing cross-platform GLib API,
- * such as e.g. GFile wrapping "ENOENT".  However, it is provided for
+ * such as e.g. #GFile wrapping %ENOENT.  However, it is provided for
  * completeness, at least.
  */
 #define G_UNIX_ERROR (g_unix_error_quark())
@@ -67,4 +71,12 @@ gboolean g_unix_pipe_flags (int      *fds,
 			    int       flags,
 			    GError  **error);
 
+GSource *g_unix_signal_source_new     (int signum);
+
+guint    g_unix_signal_add_watch_full (int            signum,
+				       int            priority,
+				       GSourceFunc    handler,
+				       gpointer       user_data,
+				       GDestroyNotify notify);
+
 #endif
diff --git a/glib/glib.symbols b/glib/glib.symbols
index be3acfa..bc1c323 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -1983,6 +1983,8 @@ g_hostname_to_unicode
 #ifdef G_OS_UNIX
 g_unix_pipe_flags
 g_unix_error_quark
+g_unix_signal_source_new
+g_unix_signal_add_watch_full
 #endif
 #endif
 #endif
diff --git a/glib/gmain-internal.h b/glib/gmain-internal.h
new file mode 100644
index 0000000..648aff3
--- /dev/null
+++ b/glib/gmain-internal.h
@@ -0,0 +1,35 @@
+/* gmain-internal.h - GLib-internal mainloop API
+ * Copyright (C) 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (GLIB_COMPILATION)
+#error "This is a private header"
+#endif
+
+#ifndef __G_MAIN_INTERNAL_H__
+#define __G_MAIN_INTERNAL_H__
+
+#include "gmain.h"
+
+G_BEGIN_DECLS
+
+GSource *_g_main_create_unix_signal_watch (int signum);
+
+G_END_DECLS
+
+#endif /* __G_MAIN_H__ */
diff --git a/glib/gmain.c b/glib/gmain.c
index 82cf810..0be3733 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -188,6 +188,7 @@
 
 typedef struct _GTimeoutSource GTimeoutSource;
 typedef struct _GChildWatchSource GChildWatchSource;
+typedef struct _GUnixSignalWatchSource GUnixSignalWatchSource;
 typedef struct _GPollRec GPollRec;
 typedef struct _GSourceCallback GSourceCallback;
 
@@ -306,6 +307,13 @@ struct _GChildWatchSource
 #endif /* G_OS_WIN32 */
 };
 
+struct _GUnixSignalWatchSource
+{
+  GSource     source;
+  int         signum;
+  gboolean    pending;
+};
+
 struct _GPollRec
 {
   GPollFD *fd;
@@ -379,6 +387,18 @@ 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 void g_unix_signal_handler (int signum);
+static void init_unix_signal_wakeup_state_unlocked (void);
+static void init_unix_signal_wakeup_state (void);
+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);
@@ -396,18 +416,42 @@ static GSList *main_contexts_without_pipe = NULL;
  * signal was received.
  */ 
 #define _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR 'C'
-/* Guards unix_signal_wake_up_pipe */
+#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);
-static gint unix_signal_wake_up_pipe[2] = {-1, -1};
-
-/* Child status monitoring code */
 enum {
-  CHILD_WATCH_UNINITIALIZED,
-  CHILD_WATCH_INITIALIZED_SINGLE,
-  CHILD_WATCH_INITIALIZED_THREADED
+  UNIX_SIGNAL_UNINITIALIZED = 0,
+  UNIX_SIGNAL_INITIALIZED_SINGLE,
+  UNIX_SIGNAL_INITIALIZED_THREADED
 };
-static gint child_watch_init_state = CHILD_WATCH_UNINITIALIZED;
+static gint unix_signal_init_state = UNIX_SIGNAL_UNINITIALIZED;
+typedef struct {
+  gboolean sigchld_handler_installed : 1;
+  gboolean sighup_handler_installed : 1;
+  gboolean sigint_handler_installed : 1;
+  gboolean sigterm_handler_installed : 1;
+  
+  /* 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 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;
@@ -4257,13 +4301,169 @@ g_child_watch_prepare (GSource *source,
   return check_for_child_exited (source);
 }
 
-
 static gboolean 
 g_child_watch_check (GSource  *source)
 {
   return check_for_child_exited (source);
 }
 
+static gboolean
+check_for_signal_delivery (GSource *source)
+{
+  GUnixSignalWatchSource *unix_signal_source = (GUnixSignalWatchSource*) source;
+  gboolean delivered;
+
+  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
+    {
+      g_assert (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED);
+      delivered = unix_signal_source->pending;
+    }
+  G_UNLOCK (unix_signal_lock);
+
+  return delivered;
+}
+
+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);
+}
+
+static gboolean
+g_unix_signal_watch_dispatch (GSource    *source, 
+			      GSourceFunc callback,
+			      gpointer    user_data)
+{
+  GUnixSignalWatchSource *unix_signal_source;
+
+  unix_signal_source = (GUnixSignalWatchSource *) source;
+
+  if (!callback)
+    {
+      g_warning ("Unix signal source dispatched without callback\n"
+		 "You must call g_source_set_callback().");
+      return FALSE;
+    }
+
+  (callback) (user_data);
+  
+  G_LOCK (unix_signal_lock);
+  if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_SINGLE)
+    {
+      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;
+	}
+    }
+  else
+    {
+      g_assert (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED);
+      unix_signal_source->pending = FALSE;
+    }
+  G_UNLOCK (unix_signal_lock);
+
+  return TRUE;
+}
+
+static void
+ensure_unix_signal_handler_installed_unlocked (int signum)
+{
+  struct sigaction action;
+
+  switch (signum)
+    {
+    case SIGHUP:
+      if (unix_signal_state.sighup_handler_installed)
+	return;
+      unix_signal_state.sighup_handler_installed = TRUE;
+      break;
+    case SIGINT:
+      if (unix_signal_state.sigint_handler_installed)
+	return;
+      unix_signal_state.sigint_handler_installed = TRUE;
+      break;
+    case SIGTERM:
+      if (unix_signal_state.sigterm_handler_installed)
+	return;
+      unix_signal_state.sigterm_handler_installed = TRUE;
+      break;
+    }
+
+  init_unix_signal_wakeup_state_unlocked ();
+
+  action.sa_handler = g_unix_signal_handler;
+  sigemptyset (&action.sa_mask);
+  action.sa_flags = 0;
+  sigaction (signum, &action, NULL);
+}
+
+GSource *
+_g_main_create_unix_signal_watch (int signum)
+{
+  GSource *source;
+  GUnixSignalWatchSource *unix_signal_source;
+
+  init_unix_signal_wakeup_state ();
+
+  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);
+
+  return source;
+}
+
+static void 
+g_unix_signal_watch_finalize (GSource    *source)
+{
+  G_LOCK (unix_signal_lock);
+  unix_signal_watches = g_slist_remove (unix_signal_watches, source);
+  G_UNLOCK (unix_signal_lock);
+}
+
 #endif /* G_OS_WIN32 */
 
 static gboolean
@@ -4292,36 +4492,75 @@ g_child_watch_dispatch (GSource    *source,
 #ifndef G_OS_WIN32
 
 static void
-g_child_watch_signal_handler (int signum)
+g_unix_signal_handler (int signum)
 {
-  child_watch_count ++;
+  if (signum == SIGCHLD)
+    child_watch_count ++;
 
-  if (child_watch_init_state == CHILD_WATCH_INITIALIZED_THREADED)
+  if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED)
     {
-      char buf[1] = { _UNIX_SIGNAL_PIPE_SIGCHLD_CHAR };
+      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.
-       */
+      /* 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
-g_child_watch_source_init_single (void)
+deliver_unix_signal (int signum)
 {
-  struct sigaction action;
+  GSList *iter;
+  g_assert (signum == SIGHUP || signum == SIGINT || signum == SIGTERM);
 
-  g_assert (! g_thread_supported());
-  g_assert (child_watch_init_state == CHILD_WATCH_UNINITIALIZED);
-
-  child_watch_init_state = CHILD_WATCH_INITIALIZED_SINGLE;
+  G_LOCK (unix_signal_lock);
+  for (iter = unix_signal_watches; iter; iter = iter->next)
+    {
+      GUnixSignalWatchSource *source = iter->data;
 
-  action.sa_handler = g_child_watch_signal_handler;
-  sigemptyset (&action.sa_mask);
-  action.sa_flags = SA_NOCLDSTOP;
-  sigaction (SIGCHLD, &action, NULL);
+      if (source->signum != signum)
+	continue;
+      
+      source->pending = TRUE;
+    }
+  G_UNLOCK (unix_signal_lock);
 }
 
 static gpointer unix_signal_helper_thread (gpointer data) G_GNUC_NORETURN;
@@ -4341,6 +4580,9 @@ unix_signal_helper_thread (gpointer data)
     {
       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)
@@ -4366,77 +4608,86 @@ unix_signal_helper_thread (gpointer data)
 	       * that info down the pipe would require a more structured
 	       * data stream (as opposed to a single byte).
 	       */
-	      _g_main_wake_up_all_contexts ();
+	      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_main_init_unix_signal_wakeup_pipe (void)
+init_unix_signal_wakeup_state_unlocked (void)
 {
   GError *error = NULL;
 
-  G_LOCK (unix_signal_lock);
-
-  if (unix_signal_wake_up_pipe[0] >= 0)
-    goto out;
+  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;
+      return;
+    }
 
-  g_assert (g_thread_supported());
+  if (unix_signal_init_state == UNIX_SIGNAL_INITIALIZED_THREADED)
+    return;
 
   if (!g_unix_pipe_flags (unix_signal_wake_up_pipe, FD_CLOEXEC, &error))
     g_error ("Cannot create UNIX signal wake up pipe: %s\n", error->message);
   fcntl (unix_signal_wake_up_pipe[1], F_SETFL, O_NONBLOCK | fcntl (unix_signal_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 (unix_signal_helper_thread, NULL, FALSE, &error) == NULL)
     g_error ("Cannot create a thread to monitor UNIX signals: %s\n", error->message);
 
- out:
-  G_UNLOCK (unix_signal_lock);
+  unix_signal_init_state = UNIX_SIGNAL_INITIALIZED_THREADED;
 }
 
 static void
-g_child_watch_source_init_multi_threaded (void)
+init_unix_signal_wakeup_state (void)
 {
-  struct sigaction action;
-  
-  _g_main_init_unix_signal_wakeup_pipe ();
-
-  child_watch_init_state = CHILD_WATCH_INITIALIZED_THREADED;
+  G_LOCK (unix_signal_lock);
 
-  action.sa_handler = g_child_watch_signal_handler;
-  sigemptyset (&action.sa_mask);
-  action.sa_flags = SA_RESTART | SA_NOCLDSTOP;
-  sigaction (SIGCHLD, &action, NULL);
-}
+  init_unix_signal_wakeup_state_unlocked ();
 
-static void
-g_child_watch_source_init_promote_single_to_threaded (void)
-{
-  g_child_watch_source_init_multi_threaded ();
+  G_UNLOCK (unix_signal_lock);
 }
 
 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
+  init_unix_signal_wakeup_state ();
+  
+  G_LOCK (unix_signal_lock);
+  if (!unix_signal_state.sigchld_handler_installed)
     {
-      if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED)
-	g_child_watch_source_init_single ();
+      struct sigaction action;
+      action.sa_handler = g_unix_signal_handler;
+      sigemptyset (&action.sa_mask);
+      action.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+      sigaction (SIGCHLD, &action, NULL);
+      unix_signal_state.sigchld_handler_installed = TRUE;
     }
+  G_UNLOCK (unix_signal_lock);
 }
 
 #endif /* !G_OS_WIN32 */
diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am
index ca42eb4..34cae64 100644
--- a/glib/tests/Makefile.am
+++ b/glib/tests/Makefile.am
@@ -171,7 +171,11 @@ sort_LDADD     = $(progs_ldadd)
 if OS_UNIX
 
 TEST_PROGS    += unix
-unix_LDADD  = $(progs_ldadd)
+unix_LDADD  = $(progs_ldadd) $(top_builddir)/gthread/libgthread-2.0.la 
+
+TEST_PROGS    += unix-nothreads
+unix_nothreads_SOURCES = unix.c
+unix_nothreads_LDADD  = $(progs_ldadd)
 
 # some testing of gtester funcitonality
 XMLLINT=xmllint
diff --git a/glib/tests/unix.c b/glib/tests/unix.c
index 2dc2364..065deb1 100644
--- a/glib/tests/unix.c
+++ b/glib/tests/unix.c
@@ -48,13 +48,94 @@ test_pipe (void)
   g_assert (g_str_has_prefix (buf, "hello"));
 }
 
+static gboolean sighup_received = FALSE;
+
+static gboolean
+on_sighup_received (gpointer user_data)
+{
+  GMainLoop *loop = user_data;
+  g_main_loop_quit (loop);
+  sighup_received = TRUE;
+  return FALSE;
+}
+
+static gboolean
+sighup_not_received (gpointer data)
+{
+  GMainLoop *loop = data;
+  (void) loop;
+  g_error ("Timed out waiting for SIGHUP");
+  return FALSE;
+}
+
+static gboolean
+exit_mainloop (gpointer data)
+{
+  GMainLoop *loop = data;
+  g_main_loop_quit (loop);
+  return FALSE;
+}
+
+static void
+test_sighup (void)
+{
+  GMainLoop *mainloop;
+
+  mainloop = g_main_loop_new (NULL, FALSE);
+
+  sighup_received = FALSE;
+  g_unix_signal_add_watch_full (SIGHUP,
+				G_PRIORITY_DEFAULT,
+				on_sighup_received,
+				mainloop,
+				NULL);
+  kill (getpid (), SIGHUP);
+  g_assert (!sighup_received);
+  g_timeout_add (5000, sighup_not_received, mainloop);
+  g_main_loop_run (mainloop);
+  g_assert (sighup_received);
+  sighup_received = FALSE;
+
+  /* Ensure we don't get double delivery */
+  g_timeout_add (500, exit_mainloop, mainloop);
+  g_main_loop_run (mainloop);
+  g_assert (!sighup_received);
+}
+
+static void
+test_sighup_add_remove (void)
+{
+  GMainLoop *mainloop;
+  guint id;
+
+  mainloop = g_main_loop_new (NULL, FALSE);
+
+  sighup_received = FALSE;
+  id = g_unix_signal_add_watch_full (SIGHUP,
+				     G_PRIORITY_DEFAULT,
+				     on_sighup_received,
+				     mainloop,
+				     NULL);
+  g_source_remove (id);
+  kill (getpid (), SIGHUP);
+  g_assert (!sighup_received);
+
+}
+
 int
 main (int   argc,
       char *argv[])
 {
   g_test_init (&argc, &argv, NULL);
 
+#ifdef TEST_THREADED
+  g_thread_init (NULL);
+#endif
+
   g_test_add_func ("/glib-unix/pipe", test_pipe);
+  g_test_add_func ("/glib-unix/sighup", test_sighup);
+  g_test_add_func ("/glib-unix/sighup_again", test_sighup);
+  g_test_add_func ("/glib-unix/sighup_add_remove", test_sighup_add_remove);
 
   return g_test_run();
 }
diff --git a/gthread/tests/Makefile.am b/gthread/tests/Makefile.am
index 387098a..436498a 100644
--- a/gthread/tests/Makefile.am
+++ b/gthread/tests/Makefile.am
@@ -13,3 +13,8 @@ TEST_PROGS	      += 1bit-emufutex
 1bit_emufutex_SOURCES  = 1bit-mutex.c
 1bit_emufutex_CFLAGS   = -DTEST_EMULATED_FUTEX
 1bit_emufutex_LDADD    = $(progs_ldadd) $(top_builddir)/gthread/libgthread-2.0.la
+
+TEST_PROGS += unix-multithreaded
+unix_multithreaded_SOURCES = $(top_srcdir)/glib/tests/unix.c
+unix_multithreaded_CFLAGS   = -DTEST_THREADED
+unix_multithreaded_LDADD    = $(progs_ldadd) $(top_builddir)/gthread/libgthread-2.0.la



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