PATCH: Making process handling more robust

Mark Mitchell mark at codesourcery.com
Thu Aug 14 10:06:45 UTC 2003


This patch improves the handling of tests that spawn new processes.
In particular, orphaned grandchildren are now killed when the child
process exits.

--
Mark Mitchell
CodeSourcery, LLC
mark at codesourcery.com


2003-08-14  Mark Mitchell  <mark at codesourcery.com>

	* qm/executable.py (TimeoutExecutable.__init__): Document -2 value
	for timeout.
	(TimeoutExecutable._InitializeChild): Create the monitor pid in
	the -2 case as well.
	(TimeoutExectuable.Run): Kill the entire process group, not just
	the monitor pid.
	(TimeoutExectuable.__UseSeparateProcessGroupForChild): New
	method.
	(Filter.__init__): Adjust documentation of timeout parameter.
	* qm/test/classes/command.py (ExecTestBase.RunProgram): Pass -2 to
	Filter when no timeout is specified.

Index: qm/executable.py
===================================================================
RCS file: /home/sc/Repository/qm/qm/executable.py,v
retrieving revision 1.12
diff -c -5 -p -r1.12 executable.py
*** qm/executable.py	14 Aug 2003 02:24:40 -0000	1.12
--- qm/executable.py	14 Aug 2003 09:59:48 -0000
*************** class TimeoutExecutable(Executable):
*** 383,422 ****
      def __init__(self, timeout = -1):
          """Construct a new 'TimeoutExecutable'.
  
          'timeout' -- The number of seconds that the child is permitted
          to run.  This value may be a floating-point value.  However,
!         the value may be rounded to an integral value on some
!         systems.  If the 'timeout' is negative, this class behaves
!         like 'Executable'."""
  
          super(TimeoutExecutable, self).__init__()
          
          # This functionality is not yet supported under Windows.
!         if timeout >= 0:
              assert sys.platform != "win32"
          
-         self.__timeout = timeout
- 
  
      def _InitializeChild(self):
  
          # Put the child into its own process group.  This step is
          # performed in both the parent and the child; therefore both
          # processes can safely assume that the creation of the process
          # group has taken place.
!         if self.__timeout >= 0:
              os.setpgid(0, 0)
  
          super(TimeoutExecutable, self)._InitializeChild()
  
  
      def _HandleChild(self):
  
          super(TimeoutExecutable, self)._HandleChild()
          
!         if self.__timeout >= 0:
              # Put the child into its own process group.  This step is
              # performed in both the parent and the child; therefore both
              # processes can safely assume that the creation of the process
              # group has taken place.
              child_pid = self._GetChildPID()
--- 383,428 ----
      def __init__(self, timeout = -1):
          """Construct a new 'TimeoutExecutable'.
  
          'timeout' -- The number of seconds that the child is permitted
          to run.  This value may be a floating-point value.  However,
!         the value may be rounded to an integral value on some systems.
!         Once the timeout expires, the child and its entire process
!         group is killed.  (The processes in the process group are sent
!         the 'SIGKILL' signal.)  If the 'timeout' is -2, the child is
!         allowed to run forever, but when it terminates the child's
!         process group is killed.
!         
!         If the 'timeout' is -1, this class behaves exactly like
!         'Executable'."""
  
          super(TimeoutExecutable, self).__init__()
          
+         self.__timeout = timeout
+ 
          # This functionality is not yet supported under Windows.
!         if self.__UseSeparateProcessGroupForChild():
              assert sys.platform != "win32"
          
  
      def _InitializeChild(self):
  
          # Put the child into its own process group.  This step is
          # performed in both the parent and the child; therefore both
          # processes can safely assume that the creation of the process
          # group has taken place.
!         if self.__UseSeparateProcessGroupForChild():
              os.setpgid(0, 0)
  
          super(TimeoutExecutable, self)._InitializeChild()
  
  
      def _HandleChild(self):
  
          super(TimeoutExecutable, self)._HandleChild()
          
!         if self.__UseSeparateProcessGroupForChild():
              # Put the child into its own process group.  This step is
              # performed in both the parent and the child; therefore both
              # processes can safely assume that the creation of the process
              # group has taken place.
              child_pid = self._GetChildPID()
*************** class TimeoutExecutable(Executable):
*** 450,463 ****
                      # Put the monitoring process into the child's process
                      # group.  We know the process group still exists at this
                      # point because either (a) we are in the process
                      # group, or (b) the parent has not yet called waitpid.
                      os.setpgid(0, child_pid)
!                     # Give the child time to run.
!                     time.sleep (self.__timeout)
!                     # Kill all processes in the child process group.
!                     os.kill(0, signal.SIGKILL)
                  finally:
                      # Exit.  This code is in a finally clause so that
                      # we are guaranteed to get here no matter what.
                      os._exit(0)
  
--- 456,473 ----
                      # Put the monitoring process into the child's process
                      # group.  We know the process group still exists at this
                      # point because either (a) we are in the process
                      # group, or (b) the parent has not yet called waitpid.
                      os.setpgid(0, child_pid)
!                     if self.__timeout >= 0:
!                         # Give the child time to run.
!                         time.sleep (self.__timeout)
!                         # Kill all processes in the child process group.
!                         os.kill(0, signal.SIGKILL)
!                     else:
!                         # This call to select will never terminate.
!                         select.select ([], [], [])
                  finally:
                      # Exit.  This code is in a finally clause so that
                      # we are guaranteed to get here no matter what.
                      os._exit(0)
  
*************** class TimeoutExecutable(Executable):
*** 471,487 ****
                                                          environment,
                                                          dir,
                                                          path)
          finally:
              # Clean up the monitoring program; it is no longer needed.
!             if self.__timeout >= 0:
!                 os.kill(self.__monitor_pid, signal.SIGKILL)
                  os.waitpid(self.__monitor_pid, 0)
                  
          return status
  
  
  
  class RedirectedExecutable(TimeoutExecutable):
      """A 'RedirectedExecutable' redirects the standard I/O streams."""
  
      def _InitializeParent(self):
--- 481,507 ----
                                                          environment,
                                                          dir,
                                                          path)
          finally:
              # Clean up the monitoring program; it is no longer needed.
!             if self.__UseSeparateProcessGroupForChild():
!                 os.kill(-self._GetChildPID(), signal.SIGKILL)
                  os.waitpid(self.__monitor_pid, 0)
                  
          return status
  
  
+     def __UseSeparateProcessGroupForChild(self):
+         """Returns true if the child wil be placed in its own process group.
+ 
+         returns -- True if the child wil be placed in its own process
+         group.  In that case, a separate monitoring process will also
+         be created."""
+         
+         return self.__timeout >= 0 or self.__timeout == -2
+ 
+ 
  
  class RedirectedExecutable(TimeoutExecutable):
      """A 'RedirectedExecutable' redirects the standard I/O streams."""
  
      def _InitializeParent(self):
*************** class Filter(RedirectedExecutable):
*** 832,843 ****
          """Create a new 'Filter'.
  
          'input' -- The string containing the input to provide to the
          child process.
  
!         'timeout' -- If non-negative, the number of seconds to wait
!         for the child to complete its processing."""
  
          super(Filter, self).__init__(timeout)
          self.__input = input
          self.__next = 0
  
--- 852,862 ----
          """Create a new 'Filter'.
  
          'input' -- The string containing the input to provide to the
          child process.
  
!         'timeout' -- As for 'TimeoutExecutable.__init__'."""
  
          super(Filter, self).__init__(timeout)
          self.__input = input
          self.__next = 0
  
Index: qm/test/classes/command.py
===================================================================
RCS file: /home/sc/Repository/qm/qm/test/classes/command.py,v
retrieving revision 1.39
diff -c -5 -p -r1.39 command.py
*** qm/test/classes/command.py	11 Aug 2003 23:33:58 -0000	1.39
--- qm/test/classes/command.py	14 Aug 2003 09:59:48 -0000
*************** class ExecTestBase(Test):
*** 174,184 ****
          'Result.PASS' or to add annotations."""
  
          # Construct the environment.
          environment = self.MakeEnvironment(context)
          # Create the executable.
!         e = qm.executable.Filter(self.stdin, self.timeout)
          # Run it.
          exit_status = e.Run(arguments, environment, path = program)
  
          # If the process terminated normally, check the outputs.
          if sys.platform == "win32" or os.WIFEXITED(exit_status):
--- 174,193 ----
          'Result.PASS' or to add annotations."""
  
          # Construct the environment.
          environment = self.MakeEnvironment(context)
          # Create the executable.
!         if self.timeout >= 0:
!             timeout = self.timeout
!         else:
!             # If no timeout was specified, we sill run this process in a
!             # separate process group and kill the entire process group
!             # when the child is done executing.  That means that
!             # orphaned child processes created by the test will be
!             # cleaned up.
!             timeout = -2
!         e = qm.executable.Filter(self.stdin, timeout)
          # Run it.
          exit_status = e.Run(arguments, environment, path = program)
  
          # If the process terminated normally, check the outputs.
          if sys.platform == "win32" or os.WIFEXITED(exit_status):



More information about the qmtest mailing list