[glib: 1/2] Ensure g_subprocess_communicate_async() never blocks




commit 4cf95e390449bfc2ecbbefdf6a9317b634114c0a
Author: Alexander Larsson <alexl redhat com>
Date:   Mon Aug 17 12:25:34 2020 +0200

    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,


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