[glib/wip/gsubprocess] GSubprocess: Add request_exit() and terminate() API
- From: Colin Walters <walters src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/gsubprocess] GSubprocess: Add request_exit() and terminate() API
- Date: Mon, 21 May 2012 21:38:51 +0000 (UTC)
commit 8856591fbfc4c41581ab7a0ac5a1acf6133a682b
Author: Colin Walters <walters verbum org>
Date: Mon May 21 17:12:55 2012 -0400
GSubprocess: Add request_exit() and terminate() API
This is now race-free when we use waitid().
https://bugzilla.gnome.org/show_bug.cgi?id=672102
docs/reference/gio/gio-sections.txt | 2 +
gio/gio.symbols | 2 +
gio/gsubprocess.c | 139 ++++++++++++++++++++++-------------
gio/gsubprocess.h | 6 ++
gio/tests/gsubprocess-testprog.c | 12 +++
gio/tests/gsubprocess.c | 52 +++++++++++++
6 files changed, 163 insertions(+), 50 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 73f96ca..e0484d7 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3934,6 +3934,8 @@ g_subprocess_add_watch
g_subprocess_add_watch_full
g_subprocess_run_sync
g_subprocess_wait_sync
+g_subprocess_request_exit
+g_subprocess_terminate
<SUBSECTION Status>
g_subprocess_get_status_code
g_subprocess_get_pid
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 64b4050..4331aec 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1088,6 +1088,7 @@ g_subprocess_new
g_subprocess_new_with_args
g_subprocess_set_environment
g_subprocess_query_success
+g_subprocess_request_exit
g_subprocess_run_sync
g_subprocess_run_sync_get_output_bytes
g_subprocess_run_sync_get_stdout_utf8
@@ -1113,6 +1114,7 @@ g_subprocess_set_standard_output_unix_fd
g_subprocess_set_working_directory
g_subprocess_start
g_subprocess_start_with_pipes
+g_subprocess_terminate
g_subprocess_unsetenv
g_subprocess_wait_sync
#ifndef G_OS_WIN32
diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c
index ab71691..faeab1f 100644
--- a/gio/gsubprocess.c
+++ b/gio/gsubprocess.c
@@ -27,6 +27,7 @@
#include "giostream.h"
#include "gmemoryinputstream.h"
#include "glibintl.h"
+#include "glib-private.h"
#include <string.h>
#ifdef G_OS_UNIX
@@ -39,8 +40,8 @@
typedef struct _GSubprocessClass GSubprocessClass;
-static gpointer
-g_subprocess_waitpid_helper (gpointer data);
+static void
+g_subprocess_unix_queue_waitpid (GSubprocess *self);
struct _GSubprocessClass {
GObjectClass parent_class;
@@ -137,16 +138,15 @@ g_subprocess_finalize (GObject *object)
{
GSubprocess *self = G_SUBPROCESS (object);
- if (self->state == G_SUBPROCESS_STATE_RUNNING
+ if (self->state > G_SUBPROCESS_STATE_BUILDING
&& !self->detached)
{
#ifdef G_OS_UNIX
- GThread *reaper_thread;
-
- reaper_thread = g_thread_new ("GSubprocess wait",
- g_subprocess_waitpid_helper,
- GINT_TO_POINTER (self->pid));
- g_thread_unref (reaper_thread);
+ /* 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.
+ */
+ g_subprocess_unix_queue_waitpid (self);
#endif
g_spawn_close_pid (self->pid);
}
@@ -1151,27 +1151,23 @@ g_subprocess_run_sync (GSubprocess *self,
#ifdef G_OS_UNIX
-/*
- * This function is called in the case where the caller didn't
- * explicitly use _set_detached() or watch the child explicitly via
- * g_subprocess_add_watch().
- *
- * We don't want to block the calling thread in waitpid(), so
- * move it to a thread.
- *
- * For more efficiency we could move this to the GLib worker thread,
- * but callers should really know to use one of _set_detached() or
- * _add_watch().
- */
-static gpointer
-g_subprocess_waitpid_helper (gpointer data)
+static gboolean
+g_subprocess_unix_waitpid_dummy (gpointer data)
{
- GPid pid = GPOINTER_TO_INT (data);
- gint status;
-
- waitpid (pid, &status, 0);
+ return FALSE;
+}
- return NULL;
+static void
+g_subprocess_unix_queue_waitpid (GSubprocess *self)
+{
+ GMainContext *worker_context;
+ GSource *waitpid_source;
+
+ 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);
}
static inline void
@@ -1331,8 +1327,6 @@ g_subprocess_start_with_pipes (GSubprocess *self,
&& self->stderr_to_stdout == FALSE,
FALSE);
- self->state = G_SUBPROCESS_STATE_RUNNING;
-
#ifdef G_OS_UNIX
if (self->stdin_path)
{
@@ -1445,6 +1439,7 @@ g_subprocess_start_with_pipes (GSubprocess *self,
goto out;
ret = TRUE;
+ self->state = G_SUBPROCESS_STATE_RUNNING;
if (stdin_pipe_fd != -1)
{
@@ -1495,18 +1490,10 @@ g_subprocess_start_with_pipes (GSubprocess *self,
* g_subprocess_get_pid:
* @self: a #GSubprocess
*
- * Returns the identifier for this child process; this function may
- * not be used if g_subprocess_set_detached() has been called.
- *
- * <warning>Due to the way GLib presently implements subprocess
- * monitoring on Unix, attempting to use e.g. kill() to send a signal
- * to the child process has a race condition where the child process
- * may not exist.</warning>
- *
- * This function can only be called when the process is currently
- * running. In particular, do not call this function after a callback
- * from a child watch such as g_subprocess_add_watch() has been
- * invoked.
+ * Returns the identifier for this child process.
+
+ * This function may not be used if g_subprocess_set_detached() has
+ * been called.
*
* Since: 2.34
*/
@@ -1514,8 +1501,8 @@ GPid
g_subprocess_get_pid (GSubprocess *self)
{
g_return_val_if_fail (G_IS_SUBPROCESS (self), 0);
- g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_RUNNING, 0);
g_return_val_if_fail (!self->detached, 0);
+ g_return_val_if_fail (self->state > G_SUBPROCESS_STATE_BUILDING, 0);
return self->pid;
}
@@ -1560,12 +1547,7 @@ g_subprocess_child_watch_func (GPid pid,
GSubprocessWatchTrampolineData *data = user_data;
data->self->status_code = status_code;
- if (data->self->state != G_SUBPROCESS_STATE_TERMINATED)
- {
- g_assert (data->self->state == G_SUBPROCESS_STATE_RUNNING);
- g_spawn_close_pid (data->self->pid);
- data->self->state = G_SUBPROCESS_STATE_TERMINATED;
- }
+ data->self->state = G_SUBPROCESS_STATE_TERMINATED;
if (data->callback)
data->callback (data->self, data->user_data);
@@ -1653,7 +1635,7 @@ g_subprocess_create_source (GSubprocess *self,
g_return_val_if_fail (self->state == G_SUBPROCESS_STATE_RUNNING, 0);
g_return_val_if_fail (!self->detached, 0);
- source = g_child_watch_source_new (self->pid);
+ source = GLIB_PRIVATE_CALL (g_child_watch_source_new_with_flags) (self->pid, _G_CHILD_WATCH_FLAGS_WNOWAIT);
g_source_set_priority (source, priority);
trampoline_data = g_new (GSubprocessWatchTrampolineData, 1);
trampoline_data->self = g_object_ref (self);
@@ -1854,6 +1836,63 @@ g_subprocess_wait_sync (GSubprocess *self,
return ret;
}
+/**
+ * g_subprocess_request_exit:
+ * @self: a #GSubprocess
+ *
+ * Use an operating-system specific method to request a graceful exit
+ * of the process. On Unix for example, this sends %SIGTERM.
+ *
+ * You can use g_subprocess_add_watch() to monitor the status of the
+ * process after calling this function.
+ *
+ * This function may not be used if g_subprocess_set_detached() has
+ * been called.
+ */
+void
+g_subprocess_request_exit (GSubprocess *self)
+{
+ g_return_if_fail (G_IS_SUBPROCESS (self));
+ g_return_if_fail (!self->detached);
+ g_return_if_fail (self->state > G_SUBPROCESS_STATE_BUILDING);
+
+ if (self->state == G_SUBPROCESS_STATE_TERMINATED)
+ return;
+
+#ifdef G_OS_UNIX
+ (void) kill (self->pid, SIGTERM);
+#endif
+}
+
+/**
+ * g_subprocess_terminate:
+ * @self: a #GSubprocess
+ *
+ * Use an operating-system specific method to attempt a forceful
+ * termination of the process. On Unix for example, this sends
+ * %SIGKILL.
+ *
+ * You can use g_subprocess_add_watch() to monitor the status of the
+ * process after calling this function.
+ *
+ * This function may not be used if g_subprocess_set_detached() has
+ * been called.
+ */
+void
+g_subprocess_terminate (GSubprocess *self)
+{
+ g_return_if_fail (G_IS_SUBPROCESS (self));
+ g_return_if_fail (!self->detached);
+ g_return_if_fail (self->state > G_SUBPROCESS_STATE_BUILDING);
+
+ if (self->state == G_SUBPROCESS_STATE_TERMINATED)
+ return;
+
+#ifdef G_OS_UNIX
+ (void) kill (self->pid, SIGKILL);
+#endif
+}
+
/**** High level wrapers ****/
typedef struct {
diff --git a/gio/gsubprocess.h b/gio/gsubprocess.h
index 72200e0..15380d6 100644
--- a/gio/gsubprocess.h
+++ b/gio/gsubprocess.h
@@ -225,6 +225,12 @@ gboolean g_subprocess_wait_sync (GSubprocess *self,
GCancellable *cancellable,
GError **error);
+GLIB_AVAILABLE_IN_2_34
+void g_subprocess_request_exit (GSubprocess *self);
+
+GLIB_AVAILABLE_IN_2_34
+void g_subprocess_terminate (GSubprocess *self);
+
/**** High level wrapers ****/
GLIB_AVAILABLE_IN_2_34
diff --git a/gio/tests/gsubprocess-testprog.c b/gio/tests/gsubprocess-testprog.c
index 63f5f4c..8879df3 100644
--- a/gio/tests/gsubprocess-testprog.c
+++ b/gio/tests/gsubprocess-testprog.c
@@ -61,6 +61,16 @@ cat_mode (int argc,
#endif
}
+static gint
+sleep_forever_mode (int argc,
+ char **argv)
+{
+ GMainLoop *loop;
+
+ loop = g_main_loop_new (NULL, TRUE);
+ g_main_loop_run (loop);
+}
+
int
main (int argc, char **argv)
{
@@ -102,6 +112,8 @@ main (int argc, char **argv)
return echo_stdout_and_stderr_mode (argc, argv);
else if (strcmp (mode, "cat") == 0)
return cat_mode (argc, argv);
+ else if (strcmp (mode, "sleep-forever") == 0)
+ return sleep_forever_mode (argc, argv);
else
{
g_printerr ("Unknown MODE %s\n", argv[1]);
diff --git a/gio/tests/gsubprocess.c b/gio/tests/gsubprocess.c
index 0f8e8d7..63d6061 100644
--- a/gio/tests/gsubprocess.c
+++ b/gio/tests/gsubprocess.c
@@ -399,6 +399,57 @@ test_argv0 (void)
*/
}
+static gboolean
+send_terminate (gpointer user_data)
+{
+ GSubprocess *proc = user_data;
+
+ g_subprocess_terminate (proc);
+}
+
+static void
+on_request_quit_exited (GSubprocess *self,
+ gpointer user_data)
+{
+ g_main_loop_quit ((GMainLoop*)user_data);
+}
+
+static void
+test_terminate (void)
+{
+ GSubprocess *proc;
+ GError *local_error = NULL;
+ GError **error = &local_error;
+ GMainLoop *loop;
+ GSource *source;
+
+ proc = get_test_subprocess ("sleep-forever");
+
+ g_subprocess_start (proc, NULL, error);
+ g_assert_no_error (local_error);
+
+ loop = g_main_loop_new (NULL, TRUE);
+
+ source = g_subprocess_add_watch (proc, on_request_quit_exited, loop);
+
+ g_timeout_add_seconds (3, send_terminate, proc);
+
+ g_main_loop_run (loop);
+
+ (void)g_subprocess_query_success (proc, error);
+ g_assert_error (local_error, G_IO_ERROR, G_IO_ERROR_SUBPROCESS_EXIT_ABNORMAL);
+ g_clear_error (error);
+
+#ifdef G_OS_UNIX
+ {
+ int scode = g_subprocess_get_status_code (proc);
+ g_assert (WIFSIGNALED (scode) && WTERMSIG (scode) == 9);
+ }
+#endif
+
+ g_object_unref (proc);
+}
+
int
main (int argc, char **argv)
{
@@ -419,6 +470,7 @@ main (int argc, char **argv)
g_test_add_func ("/gsubprocess/cat-utf8", test_cat_utf8);
g_test_add_func ("/gsubprocess/cat-non-utf8", test_cat_non_utf8);
g_test_add_func ("/gsubprocess/multi1", test_multi_1);
+ g_test_add_func ("/gsubprocess/terminate", test_terminate);
/* g_test_add_func ("/gsubprocess/argv0", test_argv0); */
return g_test_run ();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]