Bug 1176758 - Allow mozprocess to detect and kill detached child processes. r=ahal, a=test-only
authorHenrik Skupin <mail@hskupin.info>
Thu, 30 Jun 2016 16:44:56 +0200
changeset 339908 b9783a14811df799b1d5162ff9fbb1c9f4413a7e
parent 339907 632484723c99dc0ac9dcf11e25ec10e8fdb8dabf
child 339909 1434137a26bf9aed6f3f3782646240e9e06ebc47
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersahal, test-only
bugs1176758
milestone49.0a2
Bug 1176758 - Allow mozprocess to detect and kill detached child processes. r=ahal, a=test-only 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))