[glib/wip/subprocess-2013] more changes
- From: Ryan Lortie <ryanl src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/subprocess-2013] more changes
- Date: Mon, 21 Jan 2013 21:22:37 +0000 (UTC)
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]