Bug 1176758 - Allow mozprocess to detect and kill detached child processes. r?ahal
MozReview-Commit-ID: B9yfLYUZw76
--- 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))