[PATCH 2/2] Convert process watching to use GLib before we start a main loop

David Shea dshea at redhat.com
Thu Sep 25 15:07:10 UTC 2014


This way uses of GLib.child_watch_add don't silently disable process
watching.
---
 pyanaconda/iutil.py           | 76 +++++++++++++++++++++++++++++++++++--------
 pyanaconda/ui/gui/__init__.py |  3 ++
 2 files changed, 66 insertions(+), 13 deletions(-)

diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
index 621ccff..9390f83 100644
--- a/pyanaconda/iutil.py
+++ b/pyanaconda/iutil.py
@@ -36,6 +36,8 @@ from urllib import quote, unquote
 import gettext
 import signal
 
+from gi.repository import GLib
+
 from pyanaconda.flags import flags
 from pyanaconda.constants import DRACUT_SHUTDOWN_EJECT, TRANSLATIONS_UPDATE_DIR, UNSUPPORTED_HW
 from pyanaconda.regexes import URL_PARSE
@@ -391,8 +393,10 @@ def execConsole():
     except OSError as e:
         raise RuntimeError("Error running /bin/sh: " + e.strerror)
 
-# Dictionary of processes to watch in the form {pid: name, ...}
+# Dictionary of processes to watch in the form {pid: [name, GLib event source id], ...}
 _forever_pids = {}
+# Set to True if process watching is handled by GLib
+_watch_process_glib = False
 _watch_process_handler_set = False
 
 class ExitError(RuntimeError):
@@ -430,59 +434,105 @@ def _sigchld_handler(num=None, frame=None):
                 continue
 
         if pid_result:
-            proc_name = _forever_pids[child_pid]
+            proc_name = _forever_pids[child_pid][0]
             exited_pids.append(child_pid)
 
             # Convert the wait-encoded status to the format used by subprocess
             if os.WIFEXITED(status):
                 sub_status = os.WEXITSTATUS(status)
             else:
+                # subprocess uses negative return codes to indicate signal exit
                 sub_status = -os.WTERMSIG(status)
 
             exit_statuses.append((proc_name, sub_status))
 
     for child_pid in exited_pids:
+        if _forever_pids[child_pid][1]:
+            GLib.source_remove(_forever_pids[child_pid][1])
         del _forever_pids[child_pid]
 
     if exit_statuses:
         _raise_exit_error(exit_statuses)
 
+# GLib callback used with watchProcess
+def _watch_process_cb(pid, status, proc_name):
+    # Convert the wait-encoded status to the format used by subprocess
+    if os.WIFEXITED(status):
+        sub_status = os.WEXITSTATUS(status)
+    else:
+        # subprocess uses negative return codes to indicate signal exit
+        sub_status = -os.WTERMSIG(status)
+
+    _raise_exit_error([(proc_name, sub_status)])
+
 def watchProcess(proc, name):
     """Watch for a process exit, and raise a ExitError when it does.
 
-       This method installs a SIGCHLD signal handler and thus cannot be
-       used with the child_watch_add methods in GLib. Since the SIGCHLD
-       handler calls wait() on the watched process, this call cannot be
-       combined with Popen.wait() or Popen.communicate, and also doing
-       so wouldn't make a whole lot of sense.
+       This method installs a SIGCHLD signal handler and thus interferes
+       the child_watch_add methods in GLib. Use watchProcessGLib to convert
+       to GLib mode if using a GLib main loop.
+
+       Since the SIGCHLD handler calls wait() on the watched process, this call
+       cannot be combined with Popen.wait() or Popen.communicate, and also
+       doing so wouldn't make a whole lot of sense.
 
        :param proc: The Popen object for the process
        :param name: The name of the process
     """
     global _watch_process_handler_set
 
-    if not _watch_process_handler_set:
+    if not _watch_process_glib and not _watch_process_handler_set:
         signal.signal(signal.SIGCHLD, _sigchld_handler)
         _watch_process_handler_set = True
 
     # Add the PID to the dictionary
-    _forever_pids[proc.pid] = name
+    # The second item in the list is for the GLib event source id and will be
+    # replaced with the id once we have one.
+    _forever_pids[proc.pid] = [name, None]
 
-    # Check that the process didn't already exit
-    if proc.poll() is not None:
-        del _forever_pids[proc.pid]
-        _raise_exit_error([(name, proc.returncode)])
+    # If GLib is watching processes, add a watcher. child_watch_add checks if
+    # the process has already exited.
+    if _watch_process_glib:
+        _forever_pids[proc.id][1] = GLib.child_watch_add(proc.pid, _watch_process_cb, name)
+    else:
+        # Check that the process didn't already exit
+        if proc.poll() is not None:
+            del _forever_pids[proc.pid]
+            _raise_exit_error([(name, proc.returncode)])
+
+def watchProcessGLib():
+    """Convert process watching to GLib mode.
+
+       This allows anaconda modes that use GLib main loops to use
+       GLib.child_watch_add and continue to watch processes started before the
+       main loop.
+    """
+
+    global _watch_process_glib
+
+    # The first call to child_watch_add will replace our SIGCHLD handler, and
+    # child_watch_add checks if the process has already exited before it returns,
+    # which will handle processes that exit while we're in the loop.
+
+    _watch_process_glib = True
+    for child_pid in _forever_pids:
+        _forever_pids[child_pid][1] = GLib.child_watch_add(child_pid, _watch_process_cb,
+                _forever_pids[child_pid])
 
 def unwatchProcess(proc):
     """Unwatch a process watched by watchProcess.
 
        :param proc: The Popen object for the process.
     """
+    if _forever_pids[proc.pid][1]:
+        GLib.source_remove(_forever_pids[proc.pid][1])
     del _forever_pids[proc.pid]
 
 def unwatchAllProcesses():
     """Clear the watched process list."""
     global _forever_pids
+    for child_pid in _forever_pids:
+        GLib.source_remove(_forever_pids[child_pid][1])
     _forever_pids = {}
 
 def getDirSize(directory):
diff --git a/pyanaconda/ui/gui/__init__.py b/pyanaconda/ui/gui/__init__.py
index e062906..a265b51 100644
--- a/pyanaconda/ui/gui/__init__.py
+++ b/pyanaconda/ui/gui/__init__.py
@@ -684,6 +684,9 @@ class GraphicalUserInterface(UserInterface):
         # so convert them all to GLib signal handlers.
         self._convertSignals()
 
+        # Convert process watching to GLib mode so we can use GLib.child_watch_add
+        iutil.watchProcessGLib()
+
         # try to make sure a logo image is present
         self._assureLogoImage()
 
-- 
2.1.0



More information about the anaconda-patches mailing list