From a55b647ab56e29e8beeceb6f3e2ba2ff2d2fe237 Mon Sep 17 00:00:00 2001
From: Sebastien Robin <seb@nexedi.com>
Date: Wed, 19 Dec 2012 09:29:29 +0100
Subject: [PATCH] erp5testnode: make sure to kill grandchild when we kill a
 process

---
 erp5/tests/testERP5TestNode.py       |  4 +++-
 erp5/util/testnode/ProcessManager.py | 26 +++++++++++++++++++++++---
 setup.py                             |  4 +++-
 3 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/erp5/tests/testERP5TestNode.py b/erp5/tests/testERP5TestNode.py
index 3e4a24837e..0d01ddd47a 100644
--- a/erp5/tests/testERP5TestNode.py
+++ b/erp5/tests/testERP5TestNode.py
@@ -466,7 +466,9 @@ branch = foo
       self.assertEqual(result['status_code'], expected_status)
     process_manager = ProcessManager(log=self.log, max_timeout=1)
     _checkCorrectStatus(0, *['sleep','0'])
-    _checkCorrectStatus(-15, *['sleep','2'])
+    # We must make sure that if the command is too long that
+    # it will be automatically killed
+    self.assertRaises(SubprocessError, process_manager.spawn, 'sleep','3')
 
   def test_13_SlaposControlerResetSoftware(self):
     test_node = self.getTestNode()
diff --git a/erp5/util/testnode/ProcessManager.py b/erp5/util/testnode/ProcessManager.py
index a9ec40c7a1..3195805086 100644
--- a/erp5/util/testnode/ProcessManager.py
+++ b/erp5/util/testnode/ProcessManager.py
@@ -25,6 +25,7 @@
 #
 ##############################################################################
 import os
+import psutil
 import re
 import subprocess
 import threading
@@ -98,6 +99,25 @@ def subprocess_capture(p, log, log_prefix, get_output=True):
   return (p.stdout and ''.join(stdout),
           p.stderr and ''.join(stderr))
 
+def killCommand(pid):
+  """
+  To avoid letting orphaned childs, we stop the process and all it's
+  child (until childs does not change) and then we brutally kill
+  everyone at the same time
+  """
+  process = psutil.Process(pid)
+  child_set = set([x.pid for x in process.get_children(recursive=True)])
+  new_child_set = None
+  os.kill(pid, signal.SIGSTOP)
+  while new_child_set != child_set:
+    for child_pid in child_set:
+      os.kill(child_pid, signal.SIGSTOP)
+    time.sleep(1)
+    new_child_set = set([x.pid for x in process.get_children(recursive=True)])
+  for child_pid in child_set:
+    os.kill(child_pid, signal.SIGKILL)
+  os.kill(pid, signal.SIGKILL)
+
 class ProcessManager(object):
 
   stdin = file(os.devnull)
@@ -115,7 +135,7 @@ class ProcessManager(object):
     def timeoutExpired(p, log):
       if p.poll() is None:
         log('PROCESS TOO LONG OR DEAD, GOING TO BE TERMINATED')
-        p.terminate()
+        killCommand(p.pid)
 
     if self.under_cancellation:
       raise CancellationError("Test Result was cancelled")
@@ -147,7 +167,7 @@ class ProcessManager(object):
     self.process_pid_set.discard(p.pid)
     if self.under_cancellation:
       raise CancellationError("Test Result was cancelled")
-    if raise_error_if_fail and p.returncode != -15 and p.returncode:
+    if raise_error_if_fail and p.returncode:
       raise SubprocessError(result)
     return result
 
@@ -163,7 +183,7 @@ class ProcessManager(object):
       self.under_cancellation = True
     for pgpid in self.process_pid_set:
       try:
-        os.kill(pgpid, signal.SIGTERM)
+        killCommand(pgpid)
       except:
         pass
     try:
diff --git a/setup.py b/setup.py
index 54a6b63780..fcff076fd7 100644
--- a/setup.py
+++ b/setup.py
@@ -49,9 +49,10 @@ setup(name=name,
       namespace_packages=['erp5', 'erp5.util'],
       install_requires=[
         'setuptools', # namespaces
+        'psutil >= 0.5.0',
       ],
       extras_require={
-        'testnode': ['slapos.core', 'xml_marshaller'],
+        'testnode': ['slapos.core', 'xml_marshaller', 'psutil >= 0.5.0'],
         'testbrowser': ['zope.testbrowser >= 3.11.1', 'z3c.etestbrowser'],
         'benchmark': benchmark_install_require_list,
         'benchmark-report': [name+'[benchmark]', 'matplotlib', 'numpy'],
@@ -76,6 +77,7 @@ setup(name=name,
       tests_require=[
         'slapos.core',
         'xml_marshaller',
+        'psutil >= 0.5.0',
       ],
     )
 
-- 
2.30.9