From 4cf95e390449bfc2ecbbefdf6a9317b634114c0a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 17 Aug 2020 12:25:34 +0200 Subject: [PATCH] Ensure g_subprocess_communicate_async() never blocks It turns out that our async write operation implementation is broken on non-O_NONBLOCK pipes, because the default async write implementation calls write() after poll() said there were some space. However, the semantics of pipes is that unless O_NONBLOCK is set then the write *will* block if the passed in write count is larger than the available space. This caused a deadlock in https://gitlab.gnome.org/GNOME/glib/-/issues/2182 due to the loop-back of the app stdout to the parent, but even without such a deadlock it is a problem that we may block the mainloop at all. In the particular case of g_subprocess_communicate() we have full control of the pipes after starting the app, so it is safe to enable O_NONBLOCK (i.e. we can ensure all the code using the fd after this can handle non-blocking mode). This fixes https://gitlab.gnome.org/GNOME/glib/-/issues/2182 --- gio/gsubprocess.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/gio/gsubprocess.c b/gio/gsubprocess.c index b5515c705..8cc935423 100644 --- a/gio/gsubprocess.c +++ b/gio/gsubprocess.c @@ -1595,6 +1595,23 @@ g_subprocess_communicate_internal (GSubprocess *subprocess, if (subprocess->stdin_pipe) { g_assert (stdin_buf != NULL); + +#ifdef G_OS_UNIX + /* We're doing async writes to the pipe, and the async write mechanism assumes + * that streams polling as writable do SOME progress (possibly partial) and then + * stop, but never block. + * + * However, for blocking pipes, unix will return writable if there is *any* space left + * but still block until the full buffer size is available before returning from write. + * So, to avoid async blocking on the main loop we make this non-blocking here. + * + * It should be safe to change the fd because we're the only user at this point as + * per the g_subprocess_communicate() docs, and all the code called by this function + * properly handles non-blocking fds. + */ + g_unix_set_fd_nonblocking (g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (subprocess->stdin_pipe)), TRUE, NULL); +#endif + state->stdin_buf = g_memory_input_stream_new_from_bytes (stdin_buf); g_output_stream_splice_async (subprocess->stdin_pipe, (GInputStream*)state->stdin_buf, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,