[vte] pty: Fix indefinite wait for EOS after child-exited



commit 058adf5f924cce9ee1e6da2d8f3909c441d90ec1
Author: Christian Persch <chpe src gnome org>
Date:   Sun Dec 1 22:58:51 2019 +0100

    pty: Fix indefinite wait for EOS after child-exited
    
    When the child process exits, we wait for EOF on the PTY, so that we
    can read and process any data already emitted by the child process but
    not read yet at that time.
    
    However that leads to a problem when the child process created child
    processes of its own that inherited the PTY as stdin/out/err, which keeps
    the PTY open and thus we get no EOF until all such processes have exited.
    
    To work around this, install a timeout when the child process exits, and
    when the timer times out, fake en EOF.
    
    https://gitlab.gnome.org/GNOME/vte/issues/204

 src/glib-glue.hh   |  4 ++--
 src/vte.cc         | 29 +++++++++++++++++++++++++++++
 src/vteinternal.hh |  4 ++++
 3 files changed, 35 insertions(+), 2 deletions(-)
---
diff --git a/src/glib-glue.hh b/src/glib-glue.hh
index 731c3b7e..cbf7559e 100644
--- a/src/glib-glue.hh
+++ b/src/glib-glue.hh
@@ -147,8 +147,8 @@ private:
                 auto const id = m_source_id;
                 auto const rv = m_callback();
 
-                /* The Timer may have been re-scheduled from within the callback.
-                 * In this case, the callback must return false!
+                /* The Timer may have been re-scheduled or removed from within
+                 * the callback. In this case, the callback must return false!
                  * m_source_id is now different (since the old source
                  * ID is still associated with the main context until we return from
                  * this function), after which invalidate_source() will be called,
diff --git a/src/vte.cc b/src/vte.cc
index 2eb3cd51..42a0e199 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -833,6 +833,23 @@ Terminal::queue_child_exited()
                         g_object_unref);
 }
 
+bool
+Terminal::child_exited_eos_wait_callback() noexcept
+{
+        /* If we get this callback, there has been some time elapsed
+         * after child-exited, but no EOS yet. This happens for example
+         * when the primary child started other processes in the background,
+         * which inherited the PTY, and thus keep it open, see
+         * https://gitlab.gnome.org/GNOME/vte/issues/204
+         *
+         * Force an EOS.
+         */
+        if (pty())
+                pty_io_read(pty()->fd(), G_IO_HUP);
+
+        return false; // don't run again
+}
+
 /* Emit a "char-size-changed" signal. */
 void
 Terminal::emit_char_size_changed(int width,
@@ -3123,6 +3140,8 @@ Terminal::child_watch_done(pid_t pid,
         if (pty() || !m_incoming_queue.empty()) {
                 m_child_exit_status = status;
                 m_child_exited_after_eos_pending = true;
+
+                m_child_exited_eos_wait_timer.schedule_seconds(5); // FIXME: better value?
         } else {
                 m_child_exited_after_eos_pending = false;
 
@@ -4051,6 +4070,14 @@ out:
                 chunk->set_sealed();
                 chunk->set_eos();
 
+                /* Cancel wait timer */
+                m_child_exited_eos_wait_timer.abort();
+
+                /* Need to process the EOS */
+               if (!is_processing()) {
+                       add_process_timeout(this);
+               }
+
                 again = false;
         }
 
@@ -10008,6 +10035,8 @@ Terminal::unset_pty(bool notify_widget)
         disconnect_pty_read();
         disconnect_pty_write();
 
+        m_child_exited_eos_wait_timer.abort();
+
         /* Clear incoming and outgoing queues */
         m_input_bytes = 0;
         m_incoming_queue = {};
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index bab7f756..abaf7e88 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -375,6 +375,10 @@ public:
         int m_child_exit_status{-1};   /* pid's exit status, or -1 */
         bool m_eos_pending{false};
         bool m_child_exited_after_eos_pending{false};
+        bool child_exited_eos_wait_callback() noexcept;
+        vte::glib::Timer m_child_exited_eos_wait_timer{std::bind(&Terminal::child_exited_eos_wait_callback,
+                                                                 this),
+                                                       "child-exited-eos-wait-timer"};
         VteReaper *m_reaper;
 
        /* Queue of chunks of data read from the PTY.


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