[glib/gmain: 3/5] gmain: rewrite child and signal watch sources
- From: Ryan Lortie <ryanl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/gmain: 3/5] gmain: rewrite child and signal watch sources
- Date: Thu, 28 Jul 2011 04:09:23 +0000 (UTC)
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]