[glib/wip/subprocess-2013] more changes



commit 61156955e688e0350ed48da6c3dc199e0f52d2e2
Author: Ryan Lortie <desrt desrt ca>
Date:   Mon Jan 21 16:22:25 2013 -0500

    more changes

 gio/glib-compile-resources.c      |   20 +-
 gio/gsubprocess.c                 |  703 +++++++++++++++++--------------------
 gio/gsubprocess.h                 |   25 ++-
 gio/gsubprocesslauncher-private.h |    3 +
 gio/gsubprocesslauncher.c         |   53 +++
 gio/gsubprocesslauncher.h         |   11 +
 gio/tests/gsubprocess.c           |    5 +-
 glib/gmain.c                      |   26 ++
 8 files changed, 438 insertions(+), 408 deletions(-)
---
diff --git a/gio/glib-compile-resources.c b/gio/glib-compile-resources.c
index 9c04bfa..d7dd580 100644
--- a/gio/glib-compile-resources.c
+++ b/gio/glib-compile-resources.c
@@ -314,20 +314,15 @@ end_element (GMarkupParseContext  *context,
                 }
               close (fd);
 
-              proc = g_subprocess_new_simple_argl (G_SUBPROCESS_STREAM_DISPOSITION_NULL,
-                                                   G_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                                   error,
-                                                   xmllint,
-                                                   "--nonet", "--noblanks",
-                                                   "--output", tmp_file,
-                                                   real_file, NULL);
+              proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE, error,
+                                       xmllint, "--nonet", "--noblanks", "--output", tmp_file, real_file, NULL);
               g_free (real_file);
 	      real_file = NULL;
 
 	      if (!proc)
 		goto cleanup;
 
-	      if (!g_subprocess_wait_sync_check (proc, NULL, error))
+	      if (!g_subprocess_wait_check (proc, NULL, error))
 		{
 		  g_object_unref (proc);
                   goto cleanup;
@@ -365,15 +360,12 @@ end_element (GMarkupParseContext  *context,
                 }
               close (fd);
 
-              proc = g_subprocess_new_simple_argl (G_SUBPROCESS_STREAM_DISPOSITION_NULL,
-                                                   G_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                                   error,
-                                                   gdk_pixbuf_pixdata, real_file, tmp_file2,
-                                                   NULL);
+              proc = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_SILENCE, error,
+                                       gdk_pixbuf_pixdata, real_file, tmp_file2, NULL);
               g_free (real_file);
               real_file = NULL;
 
-	      if (!g_subprocess_wait_sync_check (proc, NULL, error))
+	      if (!g_subprocess_wait_check (proc, NULL, error))
 		{
 		  g_object_unref (proc);
                   goto cleanup;
diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c
index a2e4105..683e7f6 100644
--- a/gio/gsubprocess.c
+++ b/gio/gsubprocess.c
@@ -64,29 +64,50 @@
 #define O_BINARY 0
 #endif
 
+/* A GSubprocess can have two possible states: running and not.
+ *
+ * These two states are reflected by the value of 'pid'.  If it is
+ * non-zero then the process is running, with that pid.
+ *
+ * When a GSubprocess is first created with g_object_new() it is not
+ * running.  When it is finalized, it is also not running.
+ *
+ * During initable_init(), if the g_spawn() is successful then we
+ * immediately register a child watch and take an extra ref on the
+ * subprocess.  That reference doesn't drop until the child has quit,
+ * which is why finalize can only happen in the non-running state.  In
+ * the event that the g_spawn() failed we will still be finalizing a
+ * non-running GSubprocess (before returning from g_subprocess_new())
+ * with NULL.
+ *
+ * We make extensive use of the glib worker thread to guarentee
+ * race-free operation.  As with all child watches, glib calls waitpid()
+ * in the worker thread.  It reports the child exiting to us via the
+ * worker thread (which means that we can do synchronous waits without
+ * running a separate loop).  We also send signals to the child process
+ * via the worker thread so that we don't race with waitpid() and
+ * accidentally send a signal to an already-reaped child.
+ */
 static void initable_iface_init (GInitableIface         *initable_iface);
 
 typedef GObjectClass GSubprocessClass;
 
-#ifdef G_OS_UNIX
-static void
-g_subprocess_unix_queue_waitpid (GSubprocess  *self);
-#endif
-
 struct _GSubprocess
 {
   GObject parent;
 
   /* only used during construction */
+  GSubprocessLauncher *launcher;
   GSubprocessFlags flags;
   gchar **argv;
 
-  GSubprocessLauncher *launcher;
+  /* state tracking variables */
+  int exit_status;
   GPid pid;
 
-  guint pid_valid : 1;
-  guint reaped_child : 1;
-  guint unused : 30;
+  /* list of GTask */
+  GMutex pending_waits_lock;
+  GSList *pending_waits;
 
   /* These are the streams created if a pipe is requested via flags. */
   GOutputStream *stdin_pipe;
@@ -107,101 +128,41 @@ enum
 
 static GParamSpec *g_subprocess_pspecs[N_PROPS];
 
-static void
-g_subprocess_init (GSubprocess  *self)
-{
-}
-
-static void
-g_subprocess_finalize (GObject *object)
-{
-  GSubprocess *self = G_SUBPROCESS (object);
-
-  if (self->pid_valid)
-    {
-#ifdef G_OS_UNIX
-  /* Here we need to actually call waitpid() to clean up the
-   * zombie.  In case the child hasn't actually exited, defer this
-       * cleanup to the worker thread.
-       */
-      if (!self->reaped_child)
-        g_subprocess_unix_queue_waitpid (self);
-#endif
-      g_spawn_close_pid (self->pid);
-    }
-
-  g_clear_object (&self->stdin_pipe);
-  g_clear_object (&self->stdout_pipe);
-  g_clear_object (&self->stderr_pipe);
-
-  if (G_OBJECT_CLASS (g_subprocess_parent_class)->finalize != NULL)
-    G_OBJECT_CLASS (g_subprocess_parent_class)->finalize (object);
-}
 
-static void
-g_subprocess_set_property (GObject      *object,
-                           guint         prop_id,
-                           const GValue *value,
-                           GParamSpec   *pspec)
+typedef struct
 {
-  GSubprocess *self = G_SUBPROCESS (object);
-
-  switch (prop_id)
-    {
-    case PROP_FLAGS:
-      self->flags = g_value_get_flags (value);
-      break;
-
-    case PROP_ARGV:
-      self->argv = g_value_dup_boxed (value);
-      break;
-
-    default:
-      g_assert_not_reached ();
-    }
-}
+  gint                 fds[3];
+  GSpawnChildSetupFunc child_setup_func;
+  gpointer             child_setup_data;
+} ChildData;
 
 static void
-g_subprocess_class_init (GSubprocessClass *class)
+child_setup (gpointer user_data)
 {
-  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
-
-  gobject_class->finalize = g_subprocess_finalize;
-  gobject_class->set_property = g_subprocess_set_property;
-
-  g_subprocess_pspecs[PROP_FLAGS] = g_param_spec_flags ("flags", P_("Flags"), P_("Subprocess flags"),
-                                                        G_TYPE_SUBPROCESS_FLAGS, 0, G_PARAM_WRITABLE |
-                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-  g_subprocess_pspecs[PROP_ARGV] = g_param_spec_boxed ("argv", P_("Arguments"), P_("Argument vector"),
-                                                       G_TYPE_STRV, G_PARAM_WRITABLE |
-                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
-
-  g_object_class_install_properties (gobject_class, N_PROPS, g_subprocess_pspecs);
-}
-
-#ifdef G_OS_UNIX
+  ChildData *child_data = user_data;
+  gint i;
 
-static gboolean
-g_subprocess_unix_waitpid_dummy (gpointer data)
-{
-  return FALSE;
-}
+  /* We're on the child side now.  "Rename" the file descriptors in
+   * child_data.fds[] to stdin/stdout/stderr.
+   *
+   * We don't close the originals.  It's possible that the originals
+   * should not be closed and if they should be closed then they should
+   * have been created O_CLOEXEC.
+   */
+  for (i = 0; i < 3; i++)
+    if (child_data->fds[i] != -1 && child_data->fds[i] != i)
+      {
+        gint result;
 
-static void
-g_subprocess_unix_queue_waitpid (GSubprocess  *self)
-{
-  GMainContext *worker_context;
-  GSource *waitpid_source;
+        do
+          result = dup2 (child_data->fds[i], i);
+        while (result == -1 && errno == EINTR);
+      }
 
-  worker_context = GLIB_PRIVATE_CALL (g_get_worker_context) ();
-  waitpid_source = g_child_watch_source_new (self->pid); 
-  g_source_set_callback (waitpid_source, g_subprocess_unix_waitpid_dummy, NULL, NULL);
-  g_source_attach (waitpid_source, worker_context);
-  g_source_unref (waitpid_source);
+  if (child_data->child_setup_func)
+    child_data->child_setup_func (child_data->child_setup_data);
 }
 
-#endif
-
 static GInputStream *
 platform_input_stream_from_spawn_fd (gint fd)
 {
@@ -256,38 +217,54 @@ unix_open_file (const char  *filename,
 }
 #endif
 
-typedef struct
-{
-  gint             fds[3];
-  GSpawnChildSetupFunc child_setup_func;
-  gpointer             child_setup_data;
-} ChildData;
 
 static void
-child_setup (gpointer user_data)
+g_subprocess_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
 {
-  ChildData *child_data = user_data;
-  gint i;
+  GSubprocess *self = G_SUBPROCESS (object);
 
-  /* We're on the child side now.  "Rename" the file descriptors in
-   * child_data.fds[] to stdin/stdout/stderr.
-   *
-   * We don't close the originals.  It's possible that the originals
-   * should not be closed and if they should be closed then they should
-   * have been created O_CLOEXEC.
-   */
-  for (i = 0; i < 3; i++)
-    if (child_data->fds[i] != -1 && child_data->fds[i] != i)
-      {
-        gint result;
+  switch (prop_id)
+    {
+    case PROP_FLAGS:
+      self->flags = g_value_get_flags (value);
+      break;
 
-        do
-          result = dup2 (child_data->fds[i], i);
-        while (result == -1 && errno == EINTR);
-      }
+    case PROP_ARGV:
+      self->argv = g_value_dup_boxed (value);
+      break;
 
-  if (child_data->child_setup_func)
-    child_data->child_setup_func (child_data->child_setup_data);
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+static gboolean
+g_subprocess_exited (GPid     pid,
+                     gint     exit_status,
+                     gpointer user_data)
+{
+  GSubprocess *self = user_data;
+  GSList *tasks;
+
+  g_assert (self->pid == pid);
+
+  g_mutex_lock (&self->pending_waits_lock);
+  self->exit_status = exit_status;
+  tasks = self->pending_waits;
+  self->pending_waits = NULL;
+  self->pid = 0;
+  g_mutex_unlock (&self->pending_waits_lock);
+
+  while (tasks)
+    {
+      g_task_return_boolean (tasks->data, TRUE);
+      tasks = g_slist_delete_link (tasks, tasks);
+    }
+
+  return FALSE;
 }
 
 static gboolean
@@ -342,8 +319,7 @@ initable_init (GInitable     *initable,
         child_data.fds[1] = self->launcher->stdout_fd;
       else if (self->launcher->stdout_path != NULL)
         {
-          child_data.fds[1] = close_fds[1] = unix_open_file (self->launcher->stdout_path,
-                                                             O_CREAT | O_WRONLY, error);
+          child_data.fds[1] = close_fds[1] = unix_open_file (self->launcher->stdout_path, O_CREAT | O_WRONLY, error);
           if (child_data.fds[1] == -1)
             goto out;
         }
@@ -362,8 +338,7 @@ initable_init (GInitable     *initable,
         child_data.fds[2] = self->launcher->stderr_fd;
       else if (self->launcher->stderr_path != NULL)
         {
-          child_data.fds[2] = close_fds[2] = unix_open_file (self->launcher->stderr_path,
-                                                             O_CREAT | O_WRONLY, error);
+          child_data.fds[2] = close_fds[2] = unix_open_file (self->launcher->stderr_path, O_CREAT | O_WRONLY, error);
           if (child_data.fds[2] == -1)
             goto out;
         }
@@ -390,10 +365,24 @@ initable_init (GInitable     *initable,
                                       &self->pid,
                                       pipe_ptrs[0], pipe_ptrs[1], pipe_ptrs[2],
                                       error);
+  g_assert (success == (self->pid != 0));
+
   if (success)
-    self->pid_valid = TRUE;
+    {
+      GMainContext *worker_context;
+      GSource *source;
+
+      worker_context = GLIB_PRIVATE_CALL (g_get_worker_context) ();
+      source = g_child_watch_source_new (self->pid);
+      g_source_set_callback (source, (GSourceFunc) g_subprocess_exited, g_object_ref (self), g_object_unref);
+      g_source_attach (source, worker_context);
+      g_source_unref (source);
+    }
 
 out:
+  /* we don't need this past init... */
+  self->launcher = NULL;
+
   for (i = 0; i < 3; i++)
     if (close_fds[i] != -1)
       close (close_fds[i]);
@@ -406,11 +395,50 @@ out:
 }
 
 static void
+g_subprocess_finalize (GObject *object)
+{
+  GSubprocess *self = G_SUBPROCESS (object);
+
+  g_assert (self->pid == 0);
+
+  g_clear_object (&self->stdin_pipe);
+  g_clear_object (&self->stdout_pipe);
+  g_clear_object (&self->stderr_pipe);
+  g_free (self->argv);
+
+  if (G_OBJECT_CLASS (g_subprocess_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_subprocess_parent_class)->finalize (object);
+}
+
+static void
+g_subprocess_init (GSubprocess  *self)
+{
+}
+
+static void
 initable_iface_init (GInitableIface *initable_iface)
 {
   initable_iface->init = initable_init;
 }
 
+static void
+g_subprocess_class_init (GSubprocessClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+
+  gobject_class->finalize = g_subprocess_finalize;
+  gobject_class->set_property = g_subprocess_set_property;
+
+  g_subprocess_pspecs[PROP_FLAGS] = g_param_spec_flags ("flags", P_("Flags"), P_("Subprocess flags"),
+                                                        G_TYPE_SUBPROCESS_FLAGS, 0, G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+  g_subprocess_pspecs[PROP_ARGV] = g_param_spec_boxed ("argv", P_("Arguments"), P_("Argument vector"),
+                                                       G_TYPE_STRV, G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, g_subprocess_pspecs);
+}
+
 /**
  * g_subprocess_new: (skip)
  *
@@ -475,38 +503,6 @@ g_subprocess_newv (const gchar * const  *argv,
                          NULL);
 }
 
-/**
- * g_subprocess_get_pid:
- * @self: a #GSubprocess
- *
- * The identifier for this child process; it is valid as long as the
- * process @self is referenced.  In particular, do
- * <emphasis>not</emphasis> call g_spawn_close_pid() on this value;
- * that is handled internally.
- *
- * On some Unix versions, it is possible for there to be a race
- * condition where waitpid() may have been called to collect the child
- * before any watches (such as that installed by
- * g_subprocess_add_watch()) have fired.  If you are planning to use
- * native functions such as kill() on the pid, your program should
- * gracefully handle an %ESRCH result to mitigate this.
- *
- * If you want to request process termination, using the high level
- * g_subprocess_request_exit() and g_subprocess_force_exit() API is
- * recommended.
- *
- * Returns: Operating-system specific identifier for child process
- *
- * Since: 2.36
- */
-GPid
-g_subprocess_get_pid (GSubprocess     *self)
-{
-  g_return_val_if_fail (G_IS_SUBPROCESS (self), 0);
-
-  return self->pid;
-}
-
 GOutputStream *
 g_subprocess_get_stdin_pipe (GSubprocess       *self)
 {
@@ -534,153 +530,76 @@ g_subprocess_get_stderr_pipe (GSubprocess      *self)
   return self->stderr_pipe;
 }
 
-typedef struct {
+static void
+g_subprocess_wait_cancelled (GCancellable *cancellable,
+                             gpointer      user_data)
+{
+  GTask *task = user_data;
   GSubprocess *self;
-  gboolean have_wnowait;
-  GCancellable *cancellable;
-  GSimpleAsyncResult *result;
-} GSubprocessWatchData;
-
-static gboolean
-g_subprocess_on_child_exited (GPid       pid,
-			      gint       status_code,
-			      gpointer   user_data)
-{
-  GSubprocessWatchData *data = user_data;
-  GError *error = NULL;
-  
-  if (g_cancellable_set_error_if_cancelled (data->cancellable, &error))
-    {
-      g_simple_async_result_take_error (data->result, error);
-    }
-  else
-    {
-      if (!data->have_wnowait)
-	data->self->reaped_child = TRUE;
-
-      g_simple_async_result_set_op_res_gssize (data->result, status_code);
-    }
 
-  g_simple_async_result_complete (data->result);
+  self = g_task_get_source_object (task);
 
-  g_object_unref (data->result);
-  g_object_unref (data->self);
-  g_free (data);
+  g_mutex_lock (&self->pending_waits_lock);
+  self->pending_waits = g_slist_remove (self->pending_waits, task);
+  g_mutex_unlock (&self->pending_waits_lock);
 
-  return FALSE;
+  g_task_return_boolean (task, FALSE);
+  g_object_unref (task);
 }
 
-/**
- * g_subprocess_wait:
- * @self: a #GSubprocess
- * @cancellable: a #GCancellable
- * @callback: Invoked when process exits, or @cancellable is cancelled
- * @user_data: Data for @callback
- *
- * Start an asynchronous wait for the subprocess @self to exit.
- *
- * Since: 2.36
- */
 void
 g_subprocess_wait_async (GSubprocess         *self,
                          GCancellable        *cancellable,
                          GAsyncReadyCallback  callback,
                          gpointer             user_data)
 {
-  GSource *source;
-  GSubprocessWatchData *data;
-
-  data = g_new0 (GSubprocessWatchData, 1);
+  GTask *task;
 
-  data->self = g_object_ref (self);
-  data->result = g_simple_async_result_new ((GObject*)self, callback, user_data,
-					    g_subprocess_wait);
+  task = g_task_new (self, cancellable, callback, user_data);
 
-  source = GLIB_PRIVATE_CALL (g_child_watch_source_new_with_flags) (self->pid, _G_CHILD_WATCH_FLAGS_WNOWAIT);
-  if (source == NULL)
-    {
-      source = g_child_watch_source_new (self->pid);
-      data->have_wnowait = FALSE;
-    }
-  else
+  g_mutex_lock (&self->pending_waits_lock);
+  if (self->pid)
     {
-      data->have_wnowait = TRUE;
+      /* Only bother with cancellable if we're putting it in the list.
+       * If not, it's going to dispatch immediately anyway and we will
+       * see the cancellation in the _finish().
+       */
+      if (cancellable)
+        g_signal_connect_object (cancellable, "cancelled", G_CALLBACK (g_subprocess_wait_cancelled), task, 0);
+
+      self->pending_waits = g_slist_prepend (self->pending_waits, task);
+      task = NULL;
     }
+  g_mutex_unlock (&self->pending_waits_lock);
 
-  g_source_set_callback (source, (GSourceFunc)g_subprocess_on_child_exited,
-			 data, NULL);
-  if (cancellable)
+  /* If we still have task then it's because did_exit is already TRUE */
+  if (task != NULL)
     {
-      GSource *cancellable_source;
-
-      data->cancellable = g_object_ref (cancellable);
-
-      cancellable_source = g_cancellable_source_new (cancellable);
-      g_source_add_child_source (source, cancellable_source);
-      g_source_unref (cancellable_source);
+      g_task_return_boolean (task, TRUE);
+      g_object_unref (task);
     }
-
-  g_source_attach (source, g_main_context_get_thread_default ());
-  g_source_unref (source);
 }
 
-/**
- * g_subprocess_wait_finish:
- * @self: a #GSubprocess
- * @result: a #GAsyncResult
- * @out_exit_status: (out): Exit status of the process encoded in platform-specific way
- * @error: a #GError
- *
- * The exit status of the process will be stored in @out_exit_status.
- * See the documentation of g_spawn_check_exit_status() for more
- * details.
- *
- * Note that @error is not set if the process exits abnormally; you
- * must use g_spawn_check_exit_status() for that.
- *
- * Since: 2.36
- */
 gboolean
-g_subprocess_wait_finish (GSubprocess                *self,
-			  GAsyncResult               *result,
-			  int                        *out_exit_status,
-			  GError                    **error)
+g_subprocess_wait_finish (GSubprocess   *self,
+                          GAsyncResult  *result,
+                          GError       **error)
 {
-  GSimpleAsyncResult *simple;
-
-  simple = G_SIMPLE_ASYNC_RESULT (result);
-
-  if (g_simple_async_result_propagate_error (simple, error))
-    return FALSE;
-
-  *out_exit_status = g_simple_async_result_get_op_res_gssize (simple);
-  
-  return TRUE;
+  return g_task_propagate_boolean (G_TASK (result), error);
 }
 
-typedef struct {
-  GMainLoop *loop;
-  gint *exit_status_ptr;
-  gboolean caught_error;
-  GError **error;
-} GSubprocessSyncWaitData;
-
 static void
 g_subprocess_on_sync_wait_complete (GObject       *object,
-				    GAsyncResult  *result,
-				    gpointer       user_data)
+                                    GAsyncResult  *result,
+                                    gpointer       user_data)
 {
-  GSubprocessSyncWaitData *data = user_data;
-
-  if (!g_subprocess_wait_finish ((GSubprocess*)object, result, 
-				 data->exit_status_ptr, data->error))
-    data->caught_error = TRUE;
+  GAsyncResult **result_ptr = user_data;
 
-  g_main_loop_quit (data->loop);
+  *result_ptr = g_object_ref (result);
 }
 
 /**
- * g_subprocess_wait_sync:
+ * g_subprocess_wait:
  * @self: a #GSubprocess
  * @out_exit_status: (out): Platform-specific exit code
  * @cancellable: a #GCancellable
@@ -690,55 +609,55 @@ g_subprocess_on_sync_wait_complete (GObject       *object,
  * status code in @out_exit_status.  See the documentation of
  * g_spawn_check_exit_status() for how to interpret it.  Note that if
  * @error is set, then @out_exit_status will be left uninitialized.
- * 
+ *
  * Returns: %TRUE on success, %FALSE if @cancellable was cancelled
  *
  * Since: 2.36
  */
 gboolean
-g_subprocess_wait_sync (GSubprocess        *self,
-			int                *out_exit_status,
-			GCancellable       *cancellable,
-			GError            **error)
+g_subprocess_wait (GSubprocess   *self,
+                   GCancellable  *cancellable,
+                   GError       **error)
 {
-  gboolean ret = FALSE;
-  gboolean pushed_thread_default = FALSE;
-  GMainContext *context = NULL;
-  GSubprocessSyncWaitData data;
-
-  memset (&data, 0, sizeof (data));
+  GAsyncResult *result = NULL;
+  GMainContext *context;
+  gboolean success;
 
   g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
 
+  /* Synchronous waits are actually the 'more difficult' case because we
+   * need to deal with the possibility of cancellation.  That more or
+   * less implies that we need a main context (to dispatch either of the
+   * possible reasons for the operation ending).
+   *
+   * So we make one and then do this async...
+   */
+
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return FALSE;
 
+  /* We can shortcut in the case that the process already quit (but only
+   * after we checked the cancellable).
+   */
+  if (self->pid == 0)
+    return TRUE;
+
+  /* Otherwise, we need to do this the long way... */
   context = g_main_context_new ();
   g_main_context_push_thread_default (context);
-  pushed_thread_default = TRUE;
 
-  data.exit_status_ptr = out_exit_status;
-  data.loop = g_main_loop_new (context, TRUE);
-  data.error = error;
+  g_subprocess_wait_async (self, cancellable, g_subprocess_on_sync_wait_complete, &result);
 
-  g_subprocess_wait (self, cancellable,
-		     g_subprocess_on_sync_wait_complete, &data);
+  while (!result)
+    g_main_context_iteration (context, TRUE);
 
-  g_main_loop_run (data.loop);
+  g_main_context_pop_thread_default (context);
+  g_main_context_unref (context);
 
-  if (data.caught_error)
-    goto out;
+  success = g_subprocess_wait_finish (self, result, error);
+  g_object_unref (result);
 
-  ret = TRUE;
- out:
-  if (pushed_thread_default)
-    g_main_context_pop_thread_default (context);
-  if (context)
-    g_main_context_unref (context);
-  if (data.loop)
-    g_main_loop_unref (data.loop);
-
-  return ret;
+  return success;
 }
 
 /**
@@ -748,67 +667,113 @@ g_subprocess_wait_sync (GSubprocess        *self,
  * @error: a #GError
  *
  * Combines g_subprocess_wait_sync() with g_spawn_check_exit_status().
- * 
+ *
  * Returns: %TRUE on success, %FALSE if process exited abnormally, or @cancellable was cancelled
  *
  * Since: 2.36
  */
 gboolean
-g_subprocess_wait_sync_check (GSubprocess        *self,
-			      GCancellable       *cancellable,
-			      GError            **error)
+g_subprocess_wait_check (GSubprocess   *self,
+                         GCancellable  *cancellable,
+                         GError       **error)
 {
-  gboolean ret = FALSE;
-  int exit_status;
+  return g_subprocess_wait (self, cancellable, error) &&
+         g_spawn_check_exit_status (self->exit_status, error);
+}
+
+void
+g_subprocess_wait_check_async (GSubprocess         *self,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+{
+  g_subprocess_wait_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+g_subprocess_wait_check_finish (GSubprocess   *self,
+                                GAsyncResult  *result,
+                                GError       **error)
+{
+  return g_subprocess_wait_finish (self, result, error) &&
+         g_spawn_check_exit_status (self->exit_status, error);
+}
+
+#ifdef G_OS_UNIX
+typedef struct
+{
+  GSubprocess *subprocess;
+  gint signalnum;
+} SignalRecord;
+
+static gboolean
+g_subprocess_actually_send_signal (gpointer user_data)
+{
+  SignalRecord *signal_record = user_data;
+
+  /* The pid is set to zero from the worker thread as well, so we don't
+   * need to take a lock in order to prevent it from changing under us.
+   */
+  if (signal_record->subprocess->pid)
+    kill (signal_record->subprocess->pid, signal_record->signalnum);
 
-  if (!g_subprocess_wait_sync (self, &exit_status, cancellable, error))
-    goto out;
+  g_object_unref (signal_record->subprocess);
 
-  if (!g_spawn_check_exit_status (exit_status, error))
-    goto out;
+  g_slice_free (SignalRecord, signal_record);
 
-  ret = TRUE;
- out:
-  return ret;
+  return FALSE;
+}
+
+static void
+g_subprocess_dispatch_signal (GSubprocess *self,
+                              gint         signalnum)
+{
+  SignalRecord signal_record = { g_object_ref (self), signalnum };
+
+  g_return_if_fail (G_IS_SUBPROCESS (self));
+
+  /* This MUST be a lower priority than the priority that the child
+   * watch source uses in initable_init().
+   *
+   * Reaping processes, reporting the results back to GSubprocess and
+   * sending signals is all done in the glib worker thread.  We cannot
+   * have a kill() done after the reap and before the report without
+   * risking killing a process that's no longer there so the kill()
+   * needs to have the lower priority.
+   *
+   * G_PRIORITY_HIGH_IDLE is lower priority than G_PRIORITY_DEFAULT.
+   */
+  g_main_context_invoke_full (GLIB_PRIVATE_CALL (g_get_worker_context) (),
+                              G_PRIORITY_HIGH_IDLE,
+                              g_subprocess_actually_send_signal,
+                              g_slice_dup (SignalRecord, &signal_record),
+                              NULL);
 }
 
 /**
- * g_subprocess_request_exit:
+ * g_subprocess_send_signal:
  * @self: a #GSubprocess
+ * @signal_num: the signal number to send
  *
- * This API uses an operating-system specific mechanism to request
- * that the subprocess gracefully exit.  This API is not available on
- * all operating systems; for those not supported, it will do nothing
- * and return %FALSE.  Portable code should handle this situation
- * gracefully.  For example, if you are communicating via input or
- * output pipe with the child, many programs will automatically exit
- * when one of their standard input or output are closed.
- *
- * On Unix, this API sends %SIGTERM.
- *
- * A %TRUE return value does <emphasis>not</emphasis> mean the
- * subprocess has exited, merely that an exit request was initiated.
- * You can use g_subprocess_add_watch() to monitor the status of the
- * process after calling this function.
+ * Sends the UNIX signal @signal_num to the subprocess, if it is still
+ * running.
  *
- * This function returns %TRUE if the process has already exited.
+ * This API is race-free.  If the subprocess has terminated, it will not
+ * be signalled.
  *
- * Returns: %TRUE if the operation is supported, %FALSE otherwise.
+ * This API is not available on Windows.
  *
  * Since: 2.36
- */
-gboolean
-g_subprocess_request_exit (GSubprocess *self)
+ **/
+void
+g_subprocess_send_signal (GSubprocess *self,
+                          gint         signal_num)
 {
-  g_return_val_if_fail (G_IS_SUBPROCESS (self), FALSE);
+  g_return_if_fail (G_IS_SUBPROCESS (self));
 
-#ifdef G_OS_UNIX
-  (void) kill (self->pid, SIGTERM);
-  return TRUE;
-#else
-  return FALSE;
-#endif
+  g_subprocess_dispatch_signal (self, signal_num);
 }
+#endif
 
 /**
  * g_subprocess_force_exit:
@@ -821,60 +786,26 @@ g_subprocess_request_exit (GSubprocess *self)
  * the process after calling this function.
  *
  * On Unix, this function sends %SIGKILL.
- */
+ *
+ * Since: 2.36
+ **/
 void
 g_subprocess_force_exit (GSubprocess *self)
 {
   g_return_if_fail (G_IS_SUBPROCESS (self));
 
 #ifdef G_OS_UNIX
-  (void) kill (self->pid, SIGKILL);
+  g_subprocess_dispatch_signal (self, SIGKILL);
 #else
   TerminateProcess (self->pid, 1);
 #endif
 }
 
-GSubprocess *
-g_subprocess_new_simple_argl (GSubprocessStreamDisposition stdout_disposition,
-			      GSubprocessStreamDisposition stderr_disposition,
-			      GError                     **error,
-			      const gchar                 *first_arg,
-			      ...)
-{
-  GPtrArray *argv;
-  va_list args;
-  GSubprocess *result;
-
-  argv = g_ptr_array_new ();
-  va_start (args, first_arg);
-  do
-    g_ptr_array_add (argv, (gchar*)first_arg);
-  while ((first_arg = va_arg (args, const char *)) != NULL);
-
-  result = g_subprocess_new_simple_argv ((char**)argv->pdata,
-					 stdout_disposition,
-					 stderr_disposition,
-					 error);
-  g_ptr_array_free (argv, TRUE);
-  
-  return result;
-}
-
-GSubprocess *
-g_subprocess_new_simple_argv (gchar                      **argv,
-			      GSubprocessStreamDisposition stdout_disposition,
-			      GSubprocessStreamDisposition stderr_disposition,
-			      GError                     **error)
+/*< private >*/
+void
+g_subprocess_set_launcher (GSubprocess         *subprocess,
+                           GSubprocessLauncher *launcher)
 {
-  GSubprocessContext *context;
-  GSubprocess *result;
-
-  context = g_subprocess_context_new (argv);
-  g_subprocess_context_set_stdout_disposition (context, stdout_disposition);
-  g_subprocess_context_set_stderr_disposition (context, stderr_disposition);
-
-  result = g_subprocess_new (context, error);
-  g_object_unref (context);
-
-  return result;
+  subprocess->launcher = launcher;
 }
+
diff --git a/gio/gsubprocess.h b/gio/gsubprocess.h
index acd29a2..58e95c0 100644
--- a/gio/gsubprocess.h
+++ b/gio/gsubprocess.h
@@ -59,11 +59,11 @@ GInputStream *   g_subprocess_get_stdout_pipe           (GSubprocess          *s
 GLIB_AVAILABLE_IN_2_36
 GInputStream *   g_subprocess_get_stderr_pipe           (GSubprocess          *self);
 
+#ifdef G_OS_UNIX
 GLIB_AVAILABLE_IN_2_36
-GPid             g_subprocess_get_pid                   (GSubprocess          *self);
-
-GLIB_AVAILABLE_IN_2_36
-void             g_subprocess_request_exit              (GSubprocess          *self);
+void             g_subprocess_send_signal               (GSubprocess          *self,
+                                                         gint                  signal_num);
+#endif
 
 GLIB_AVAILABLE_IN_2_36
 void             g_subprocess_force_exit                (GSubprocess          *self);
@@ -85,6 +85,23 @@ gboolean         g_subprocess_wait_finish               (GSubprocess          *s
                                                          GError              **error);
 
 GLIB_AVAILABLE_IN_2_36
+gboolean         g_subprocess_wait_check                (GSubprocess          *self,
+                                                         GCancellable         *cancellable,
+                                                         GError              **error);
+
+GLIB_AVAILABLE_IN_2_36
+void             g_subprocess_wait_check_async          (GSubprocess          *self,
+                                                         GCancellable         *cancellable,
+                                                         GAsyncReadyCallback   callback,
+                                                         gpointer              user_data);
+
+GLIB_AVAILABLE_IN_2_36
+gboolean         g_subprocess_wait_check_finish         (GSubprocess          *self,
+                                                         GAsyncResult         *result,
+                                                         GError              **error);
+
+
+GLIB_AVAILABLE_IN_2_36
 gboolean         g_subprocess_get_successful            (GSubprocess          *self);
 
 GLIB_AVAILABLE_IN_2_36
diff --git a/gio/gsubprocesslauncher-private.h b/gio/gsubprocesslauncher-private.h
index 812722f..0d60124 100644
--- a/gio/gsubprocesslauncher-private.h
+++ b/gio/gsubprocesslauncher-private.h
@@ -49,6 +49,9 @@ struct _GSubprocessLauncher
 #endif
 };
 
+void g_subprocess_set_launcher (GSubprocess         *subprocess,
+                                GSubprocessLauncher *launcher);
+
 G_END_DECLS
 
 #endif
diff --git a/gio/gsubprocesslauncher.c b/gio/gsubprocesslauncher.c
index 9ebb9e8..df23888 100644
--- a/gio/gsubprocesslauncher.c
+++ b/gio/gsubprocesslauncher.c
@@ -43,6 +43,8 @@
 
 #include "gsubprocesslauncher-private.h"
 #include "gioenumtypes.h"
+#include "gsubprocess.h"
+#include "ginitable.h"
 
 typedef GObjectClass GSubprocessLauncherClass;
 
@@ -534,3 +536,54 @@ g_subprocess_launcher_set_child_setup (GSubprocessLauncher  *self,
   self->child_setup_destroy_notify = destroy_notify;
 }
 #endif
+
+GSubprocess *
+g_subprocess_launcher_spawn (GSubprocessLauncher  *launcher,
+                             GError              **error,
+                             const gchar          *argv0,
+                             ...)
+{
+  GSubprocess *result;
+  GPtrArray *args;
+  const gchar *arg;
+  va_list ap;
+
+  g_return_val_if_fail (argv0 != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  args = g_ptr_array_new ();
+
+  va_start (ap, argv0);
+  g_ptr_array_add (args, (gchar *) argv0);
+  while ((arg = va_arg (ap, const gchar *)))
+    g_ptr_array_add (args, (gchar *) arg);
+
+  result = g_subprocess_launcher_spawnv (launcher, (const gchar * const *) args->pdata, error);
+
+  g_ptr_array_free (args, TRUE);
+
+  return result;
+
+}
+
+GSubprocess *
+g_subprocess_launcher_spawnv (GSubprocessLauncher  *launcher,
+                              const gchar * const  *argv,
+                              GError              **error)
+{
+  GSubprocess *subprocess;
+
+  subprocess = g_object_new (G_TYPE_SUBPROCESS,
+                             "argv", argv,
+                             "flags", launcher->flags,
+                             NULL);
+  g_subprocess_set_launcher (subprocess, launcher);
+
+  if (!g_initable_init (G_INITABLE (subprocess), NULL, error))
+    {
+      g_object_unref (subprocess);
+      return NULL;
+    }
+
+  return subprocess;
+}
diff --git a/gio/gsubprocesslauncher.h b/gio/gsubprocesslauncher.h
index 642f8bc..169edaa 100644
--- a/gio/gsubprocesslauncher.h
+++ b/gio/gsubprocesslauncher.h
@@ -44,6 +44,17 @@ GLIB_AVAILABLE_IN_2_36
 GSubprocessLauncher *   g_subprocess_launcher_new                       (void);
 
 GLIB_AVAILABLE_IN_2_36
+GSubprocess *           g_subprocess_launcher_spawn                     (GSubprocessLauncher   *self,
+                                                                         GError               **error,
+                                                                         const gchar           *argv0,
+                                                                         ...);
+
+GLIB_AVAILABLE_IN_2_36
+GSubprocess *           g_subprocess_launcher_spawnv                    (GSubprocessLauncher   *self,
+                                                                         const gchar * const   *argv,
+                                                                         GError               **error);
+
+GLIB_AVAILABLE_IN_2_36
 void                    g_subprocess_launcher_set_environ               (GSubprocessLauncher   *self,
                                                                          gchar                **environ);
 
diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c
index 0c67591..26e5e15 100644
--- a/gio/tests/gsubprocess.c
+++ b/gio/tests/gsubprocess.c
@@ -54,10 +54,7 @@ test_noop (void)
   GSubprocess *proc;
 
   args = get_test_subprocess_args ("noop", NULL);
-  proc = g_subprocess_new_simple_argv ((gchar**) args->pdata,
-                                       G_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                       G_SUBPROCESS_STREAM_DISPOSITION_INHERIT,
-                                       error);
+  proc = g_subprocess_newv ((const gchar * const *) args->pdata, G_SUBPROCESS_FLAGS_NONE, error);
   g_ptr_array_free (args, TRUE);
   g_assert_no_error (local_error);
 
diff --git a/glib/gmain.c b/glib/gmain.c
index 9f9eccc..0017bbc 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -433,6 +433,7 @@ 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 *subprocess_watches;
 
 static GSourceFuncs g_unix_signal_funcs =
 {
@@ -4786,6 +4787,8 @@ dispatch_unix_signals (void)
   /* handle GChildWatchSource instances */
   if (unix_signal_pending[SIGCHLD])
     {
+      GSList **node_ptr;
+
       unix_signal_pending[SIGCHLD] = FALSE;
 
       /* The only way we can do this is to scan all of the children.
@@ -4822,6 +4825,29 @@ dispatch_unix_signals (void)
               while (pid == -1 && errno == EINTR);
             }
         }
+
+      node_ptr = &subprocess_watches;
+      while (*node_ptr)
+        {
+          SubprocessWatch *watch = (*node)->data;
+          int si_code, si_status;
+          gboolean reaped;
+
+          if (call_waitid (watch->pid, &si_code, &si_status, &reaped))
+            {
+              /* The child has quit.  Dispatch it and remove it from the
+               * list.
+               */
+              watch->callback (si_code, si_status, reaped, watch->user_data);
+              *node_ptr = g_slist_remove (*node_ptr, watch);
+              g_slice_free (SubprocessWatch, watch);
+            }
+          else
+            {
+              /* Move to the next... */
+              *node_ptr = &(*node_ptr)->next;
+            }
+        }
     }
 
   /* handle GUnixSignalWatchSource instances */



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