[glib/wip/child-catchall] g_unix_catchall_child_source_new: New API to do waitpid(-1, ...)
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/child-catchall] g_unix_catchall_child_source_new: New API to do waitpid(-1, ...)
- Date: Thu, 25 Oct 2012 22:16:08 +0000 (UTC)
commit c5d32c4098a428f7dce14b017442c13b1bb39117
Author: Colin Walters <walters verbum org>
Date: Thu Oct 25 02:54:05 2012 -0400
g_unix_catchall_child_source_new: New API to do waitpid(-1, ...)
[STILL EXPERIMENTAL]
With the goal of modifying gnome-session to use the new Linux
PR_SET_CHILD_SUBREAPER, it needs to be able to reap arbitary children
that may be reparented to it.
But if gnome-session is going to continue to work with GLib and GIO,
even if we converted all child watches in gnome-session to use this
new API, we'd still have to contend with the possibility of it having
a process spawned indirectly (stuff like the GDBus dbus-launch
invocation).
There is the separate but related issue of continuing to support
spawning processes in multiple threads. gnome-session should be able
to call g_spawn_sync() from a helper thread, without its waitpid()
invocation racing with the waitpid(-1, ...) from the GLib worker
thread.
In order to make that case work, explicitly block the waitpid(-1, )
while a g_spawn_sync() is in progress. This isn't ideal, but it's
better than being racy.
glib/glib-unix.h | 3 +
glib/gmain-internal.h | 3 +
glib/gmain.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++---
glib/gspawn.c | 10 ++-
glib/tests/unix.c | 52 +++++++++++++
5 files changed, 255 insertions(+), 13 deletions(-)
---
diff --git a/glib/glib-unix.h b/glib/glib-unix.h
index c04f66f..0b2e3d2 100644
--- a/glib/glib-unix.h
+++ b/glib/glib-unix.h
@@ -85,6 +85,9 @@ guint g_unix_signal_add (gint signum,
GSourceFunc handler,
gpointer user_data);
+GLIB_AVAILABLE_IN_2_36
+GSource *g_unix_catchall_child_source_new (void);
+
G_END_DECLS
#endif /* __G_UNIX_H__ */
diff --git a/glib/gmain-internal.h b/glib/gmain-internal.h
index 648aff3..ce799c6 100644
--- a/glib/gmain-internal.h
+++ b/glib/gmain-internal.h
@@ -30,6 +30,9 @@ G_BEGIN_DECLS
GSource *_g_main_create_unix_signal_watch (int signum);
+void _g_main_disable_catchall_child_watch (void);
+void _g_main_enable_catchall_child_watch (void);
+
G_END_DECLS
#endif /* __G_MAIN_H__ */
diff --git a/glib/gmain.c b/glib/gmain.c
index c0c4581..d15b811 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -185,6 +185,7 @@
typedef struct _GTimeoutSource GTimeoutSource;
typedef struct _GChildWatchSource GChildWatchSource;
typedef struct _GUnixSignalWatchSource GUnixSignalWatchSource;
+typedef struct _GUnixCatchallChildWatchSource GUnixCatchallChildWatchSource;
typedef struct _GPollRec GPollRec;
typedef struct _GSourceCallback GSourceCallback;
@@ -303,6 +304,12 @@ struct _GUnixSignalWatchSource
gboolean pending;
};
+struct _GUnixCatchallChildWatchSource
+{
+ GSource source;
+ GSList *pending_waits;
+};
+
struct _GPollRec
{
GPollFD *fd;
@@ -395,6 +402,13 @@ static gboolean g_unix_signal_watch_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data);
static void g_unix_signal_watch_finalize (GSource *source);
+static gboolean g_unix_catchall_watch_prepare (GSource *source,
+ gint *timeout);
+static gboolean g_unix_catchall_watch_check (GSource *source);
+static gboolean g_unix_catchall_watch_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data);
+static void g_unix_catchall_watch_finalize (GSource *source);
#endif
static gboolean g_idle_prepare (GSource *source,
gint *timeout);
@@ -428,6 +442,15 @@ static volatile int any_unix_signal_pending;
G_LOCK_DEFINE_STATIC (unix_signal_lock);
static GSList *unix_signal_watches;
static GSList *unix_child_watches;
+static GSList *unix_catchall_child_watches;
+
+typedef struct {
+ pid_t pid;
+ gint estatus;
+ volatile gint refcount; /* atomic */
+} UnixWaitStatus;
+
+static volatile gint unix_catchall_disable_counter; /* Access via g_atomic_int */
static GSourceFuncs g_unix_signal_funcs =
{
@@ -436,6 +459,14 @@ static GSourceFuncs g_unix_signal_funcs =
g_unix_signal_watch_dispatch,
g_unix_signal_watch_finalize
};
+
+static GSourceFuncs g_unix_catchall_funcs =
+{
+ g_unix_catchall_watch_prepare,
+ g_unix_catchall_watch_check,
+ g_unix_catchall_watch_dispatch,
+ g_unix_catchall_watch_finalize
+};
#endif /* !G_OS_WIN32 */
G_LOCK_DEFINE_STATIC (main_context_list);
static GSList *main_context_list = NULL;
@@ -4405,15 +4436,19 @@ dispatch_unix_signals (void)
/* handle GChildWatchSource instances */
if (unix_signal_pending[SIGCHLD])
{
+ int estatus;
+ pid_t pid;
+
unix_signal_pending[SIGCHLD] = FALSE;
/* The only way we can do this is to scan all of the children.
*
- * The docs promise that we will not reap children that we are not
- * explicitly watching, so that ties our hands from calling
- * waitpid(-1). We also can't use siginfo's si_pid field since if
- * multiple SIGCHLD arrive at the same time, one of them can be
- * dropped (since a given UNIX signal can only be pending once).
+ * The docs promise that we will not reap children that we are
+ * not explicitly watching, so that ties our hands from calling
+ * waitpid(-1) - that is a separate _catchall API. We also
+ * can't use siginfo's si_pid field since if multiple SIGCHLD
+ * arrive at the same time, one of them can be dropped (since a
+ * given UNIX signal can only be pending once).
*/
for (node = unix_child_watches; node; node = node->next)
{
@@ -4429,6 +4464,40 @@ dispatch_unix_signals (void)
}
}
}
+
+ /* Now see if we have at least one catchall watch, and it isn't
+ * currently blocked internally. If both are true, we can
+ * freely call waitpid(-1).
+ */
+ if (unix_catchall_child_watches &&
+ g_atomic_int_get (&unix_catchall_disable_counter) == 0)
+ {
+ gboolean caught_one = FALSE;
+
+ while ((pid = waitpid (-1, &estatus, WNOHANG)) > 0)
+ {
+ caught_one = TRUE;
+
+ for (node = unix_catchall_child_watches; node; node = node->next)
+ {
+ GUnixCatchallChildWatchSource *source = node->data;
+ UnixWaitStatus *waitstatus = g_slice_new (UnixWaitStatus);
+ waitstatus->pid = pid;
+ waitstatus->estatus = estatus;
+
+ source->pending_waits = g_slist_prepend (source->pending_waits, waitstatus);
+ }
+ }
+
+ if (caught_one)
+ {
+ for (node = unix_catchall_child_watches; node; node = node->next)
+ {
+ GUnixCatchallChildWatchSource *source = node->data;
+ wake_source ((GSource *) source);
+ }
+ }
+ }
}
/* handle GUnixSignalWatchSource instances */
@@ -4472,6 +4541,14 @@ g_child_watch_check (GSource *source)
return child_watch_source->child_exited;
}
+static void
+g_child_watch_finalize (GSource *source)
+{
+ G_LOCK (unix_signal_lock);
+ unix_child_watches = g_slist_remove (unix_child_watches, source);
+ G_UNLOCK (unix_signal_lock);
+}
+
static gboolean
g_unix_signal_watch_prepare (GSource *source,
gint *timeout)
@@ -4518,6 +4595,14 @@ g_unix_signal_watch_dispatch (GSource *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);
+}
+
+static void
ensure_unix_signal_handler_installed_unlocked (int signum)
{
static sigset_t installed_signal_mask;
@@ -4565,23 +4650,116 @@ _g_main_create_unix_signal_watch (int signum)
return source;
}
+static gboolean
+g_unix_catchall_watch_prepare (GSource *source,
+ gint *timeout)
+{
+ GUnixCatchallChildWatchSource *catchall_source;
+
+ catchall_source = (GUnixCatchallChildWatchSource *) source;
+
+ return catchall_source->pending_waits != NULL;
+}
+
+static gboolean
+g_unix_catchall_watch_check (GSource *source)
+{
+ GUnixCatchallChildWatchSource *catchall_source;
+
+ catchall_source = (GUnixCatchallChildWatchSource *) source;
+
+ return catchall_source->pending_waits != NULL;
+}
+
+static gboolean
+g_unix_catchall_watch_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ GUnixCatchallChildWatchSource *catchall_source;
+ GChildWatchFunc child_callback = (GChildWatchFunc) callback;
+ GSList *iter;
+
+ catchall_source = (GUnixCatchallChildWatchSource *) source;
+
+ if (!callback)
+ {
+ g_warning ("Unix catchall source dispatched without callback\n"
+ "You must call g_source_set_callback().");
+ return FALSE;
+ }
+
+ for (iter = catchall_source->pending_waits; iter; iter = iter->next)
+ {
+ UnixWaitStatus *status = iter->data;
+
+ (child_callback) (status->pid, status->estatus, user_data);
+
+ g_slice_free (UnixWaitStatus, status);
+ }
+
+ g_clear_pointer (&catchall_source->pending_waits, (GDestroyNotify) g_slist_free);
+
+ return TRUE;
+}
+
static void
-g_unix_signal_watch_finalize (GSource *source)
+g_unix_catchall_watch_finalize (GSource *source)
{
G_LOCK (unix_signal_lock);
- unix_signal_watches = g_slist_remove (unix_signal_watches, source);
+ unix_catchall_child_watches = g_slist_remove (unix_catchall_child_watches, source);
G_UNLOCK (unix_signal_lock);
}
-static void
-g_child_watch_finalize (GSource *source)
+/**
+ * g_unix_catchall_child_source_new:
+ *
+ * Normally, g_child_watch_add() is used to monitor child processes.
+ * This function exists to support very unusual circumstances, such as
+ * writing a process that behaves like a traditional Unix "init"
+ * program, where the operating system kernel will cause "grandchild"
+ * processes to be reparented if their parent terminates.
+ *
+ * In other words, this function provides the equivalent of
+ * <literal>waitpid(-1, ...)</literal>. However, the source is only
+ * triggered for child PIDs which do not already have an active child
+ * watch.
+ *
+ * On Linux, it makes sense to use this together with
+ * <literal>prctl(PR_SET_CHILD_SUBREAPER)</literal>.
+ *
+ * Returns: A new #GSource
+ *
+ * Since: 2.36
+ */
+GSource *
+g_unix_catchall_child_source_new (void)
{
+ GSource *source;
+
+ source = g_source_new (&g_unix_catchall_funcs, sizeof (GUnixCatchallChildWatchSource));
+
G_LOCK (unix_signal_lock);
- unix_child_watches = g_slist_remove (unix_child_watches, source);
+ ensure_unix_signal_handler_installed_unlocked (SIGCHLD);
+ unix_catchall_child_watches = g_slist_prepend (unix_catchall_child_watches, source);
G_UNLOCK (unix_signal_lock);
+
+ return source;
}
-#endif /* G_OS_WIN32 */
+void
+_g_main_disable_catchall_child_watch (void)
+{
+ g_atomic_int_inc (&unix_catchall_disable_counter);
+}
+
+void
+_g_main_enable_catchall_child_watch (void)
+{
+ g_atomic_int_add (&unix_catchall_disable_counter, -1);
+}
+
+#endif /* !G_OS_WIN32 */
static gboolean
g_child_watch_dispatch (GSource *source,
diff --git a/glib/gspawn.c b/glib/gspawn.c
index 14d1b4c..4d6925c 100644
--- a/glib/gspawn.c
+++ b/glib/gspawn.c
@@ -48,6 +48,7 @@
#include "gmem.h"
#include "gshell.h"
#include "gstring.h"
+#include "gmain-internal.h"
#include "gstrfuncs.h"
#include "gtestutils.h"
#include "gutils.h"
@@ -265,7 +266,7 @@ g_spawn_sync (const gchar *working_directory,
gint ret;
GString *outstr = NULL;
GString *errstr = NULL;
- gboolean failed;
+ gboolean failed = TRUE;
gint status;
g_return_val_if_fail (argv != NULL, FALSE);
@@ -283,6 +284,8 @@ g_spawn_sync (const gchar *working_directory,
if (standard_error)
*standard_error = NULL;
+
+ _g_main_disable_catchall_child_watch ();
if (!fork_exec_with_pipes (FALSE,
working_directory,
@@ -302,7 +305,7 @@ g_spawn_sync (const gchar *working_directory,
standard_output ? &outpipe : NULL,
standard_error ? &errpipe : NULL,
error))
- return FALSE;
+ goto out;
/* Read data from child. */
@@ -438,6 +441,9 @@ g_spawn_sync (const gchar *working_directory,
}
}
}
+
+ out:
+ _g_main_enable_catchall_child_watch ();
if (failed)
{
diff --git a/glib/tests/unix.c b/glib/tests/unix.c
index 329e19a..b41f091 100644
--- a/glib/tests/unix.c
+++ b/glib/tests/unix.c
@@ -147,6 +147,57 @@ test_sighup_add_remove (void)
}
+typedef struct {
+ GPid pid;
+ GMainLoop *loop;
+} CatchAllData;
+
+static void
+on_catchall_child (GPid pid,
+ gint estatus,
+ gpointer user_data)
+{
+ CatchAllData *data = user_data;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+
+ g_spawn_check_exit_status (estatus, error);
+ g_assert_no_error (local_error);
+
+ if (pid == data->pid)
+ g_main_loop_quit (data->loop);
+}
+
+static void
+test_catchall_source (void)
+{
+ GMainLoop *mainloop;
+ CatchAllData data;
+ GSource *source;
+ GPid pid;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+ char *child_args[] = { "/bin/true", "/bin/true", NULL };
+
+ memset (&data, 0, sizeof (data));
+ mainloop = g_main_loop_new (NULL, FALSE);
+ data.loop = mainloop;
+
+ source = g_unix_catchall_child_source_new ();
+ g_source_set_callback (source, (GSourceFunc)on_catchall_child, &data, NULL);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ g_spawn_async (NULL, (char**)child_args, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid, error);
+ g_assert_no_error (local_error);
+ data.pid = pid;
+
+ g_main_loop_run (mainloop);
+
+ g_main_loop_unref (mainloop);
+}
+
int
main (int argc,
char *argv[])
@@ -159,6 +210,7 @@ main (int argc,
g_test_add_func ("/glib-unix/sigterm", test_sigterm);
g_test_add_func ("/glib-unix/sighup_again", test_sighup);
g_test_add_func ("/glib-unix/sighup_add_remove", test_sighup_add_remove);
+ g_test_add_func ("/glib-unix/catchall-source", test_catchall_source);
return g_test_run();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]