Bug 1176758 - Allow mozprocess to detect and kill detached child processes. r?ahal draft
authorHenrik Skupin <mail@hskupin.info>
Thu, 30 Jun 2016 16:44:56 +0200
changeset 384206 9153eca1ca098a982d20e1583b9fd53a2804b084
parent 383561 990aca9e4d11f4973f71e438f324579e3f217cae
child 384207 642e783ecf66f4f6743115ed2f5bf761f7b3063f
push id22206
push userbmo:hskupin@gmail.com
push dateTue, 05 Jul 2016 20:35:17 +0000
reviewersahal
bugs1176758
milestone50.0a1
Bug 1176758 - Allow mozprocess to detect and kill detached child processes. r?ahal MozReview-Commit-ID: B9yfLYUZw76
testing/mozbase/mozprocess/mozprocess/processhandler.py
--- a/testing/mozbase/mozprocess/mozprocess/processhandler.py
+++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py
@@ -141,28 +141,29 @@ class ProcessHandlerMixin(object):
                     except:
                         traceback.print_exc()
                         raise OSError("Could not terminate process")
                     finally:
                         winprocess.GetExitCodeProcess(self._handle)
                         self._cleanup()
             else:
                 def send_sig(sig):
+                    pid = self.detached_pid or self.pid
                     if not self._ignore_children:
                         try:
-                            os.killpg(self.pid, sig)
+                            os.killpg(pid, sig)
                         except BaseException as e:
                             # Error 3 is a "no such process" failure, which is fine because the
                             # application might already have been terminated itself. Any other
                             # error would indicate a problem in killing the process.
                             if getattr(e, "errno", None) != 3:
                                 print >> sys.stderr, "Could not terminate process: %s" % self.pid
                                 raise
                     else:
-                        os.kill(self.pid, sig)
+                        os.kill(pid, sig)
 
                 if sig is None and isPosix:
                     # ask the process for termination and wait a bit
                     send_sig(signal.SIGTERM)
                     limit = time.time() + self.TIMEOUT_BEFORE_SIGKILL
                     while time.time() <= limit:
                         if self.poll() is not None:
                             # process terminated nicely
@@ -715,16 +716,21 @@ falling back to not using job objects fo
                     ignore_children=self._ignore_children)
 
         # build process arguments
         args.update(self.keywordargs)
 
         # launch the process
         self.proc = self.Process([self.cmd] + self.args, **args)
 
+        if isPosix:
+            # Keep track of the initial process group in case the process detaches itself
+            self.proc.pgid = os.getpgid(self.proc.pid)
+            self.proc.detached_pid = None
+
         self.processOutput(timeout=timeout, outputTimeout=outputTimeout)
 
     def kill(self, sig=None):
         """
         Kills the managed process.
 
         If you created the process with 'ignore_children=False' (the
         default) then it will also also kill all child processes spawned by
@@ -823,16 +829,46 @@ falling back to not using job objects fo
         print >> sys.stderr, "MOZPROCESS WARNING: ProcessHandler.waitForFinish() is deprecated, " \
                              "use ProcessHandler.wait() instead"
         return self.wait(timeout=timeout)
 
     @property
     def pid(self):
         return self.proc.pid
 
+    def check_for_detached(self, new_pid):
+        """Check if the current process has been detached and mark it appropriately.
+
+        In case of application restarts the process can spawn itself into a new process group.
+        From now on the process can no longer be tracked by mozprocess anymore and has to be
+        marked as detached. If the consumer of mozprocess still knows the new process id it could
+        check for the detached state.
+
+        new_pid is the new process id of the child process.
+        """
+        if not self.proc:
+            return
+
+        if isPosix:
+            new_pgid = None
+            try:
+                new_pgid = os.getpgid(new_pid)
+            except OSError as e:
+                # Do not consume errors except "No such process"
+                if e.errno != 3:
+                    raise
+
+            if new_pgid and new_pgid != self.proc.pgid:
+                self.proc.detached_pid = new_pid
+                print >> sys.stdout, \
+                    'Child process with id "%s" has been marked as detached because it is no ' \
+                    'longer in the managed process group. Keeping reference to the process id ' \
+                    '"%s" which is the new child process.' % (self.pid, new_pid)
+
+
 class CallableList(list):
     def __call__(self, *args, **kwargs):
         for e in self:
             e(*args, **kwargs)
 
     def __add__(self, lst):
         return CallableList(list.__add__(self, lst))