addon-sdk/source/python-lib/mozrunner/killableprocess.py

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 # killableprocess - subprocesses which can be reliably killed
     2 #
     3 # Parts of this module are copied from the subprocess.py file contained
     4 # in the Python distribution.
     5 #
     6 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
     7 #
     8 # Additions and modifications written by Benjamin Smedberg
     9 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
    10 # <http://www.mozilla.org/>
    11 #
    12 # More Modifications
    13 # Copyright (c) 2006-2007 by Mike Taylor <bear@code-bear.com>
    14 # Copyright (c) 2007-2008 by Mikeal Rogers <mikeal@mozilla.com>
    15 #
    16 # By obtaining, using, and/or copying this software and/or its
    17 # associated documentation, you agree that you have read, understood,
    18 # and will comply with the following terms and conditions:
    19 #
    20 # Permission to use, copy, modify, and distribute this software and
    21 # its associated documentation for any purpose and without fee is
    22 # hereby granted, provided that the above copyright notice appears in
    23 # all copies, and that both that copyright notice and this permission
    24 # notice appear in supporting documentation, and that the name of the
    25 # author not be used in advertising or publicity pertaining to
    26 # distribution of the software without specific, written prior
    27 # permission.
    28 #
    29 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
    30 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
    31 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
    32 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
    33 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
    34 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
    35 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    37 """killableprocess - Subprocesses which can be reliably killed
    39 This module is a subclass of the builtin "subprocess" module. It allows
    40 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
    42 It also adds a timeout argument to Wait() for a limited period of time before
    43 forcefully killing the process.
    45 Note: On Windows, this module requires Windows 2000 or higher (no support for
    46 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
    47 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
    48 """
    50 import subprocess
    51 import sys
    52 import os
    53 import time
    54 import datetime
    55 import types
    56 import exceptions
    58 try:
    59     from subprocess import CalledProcessError
    60 except ImportError:
    61     # Python 2.4 doesn't implement CalledProcessError
    62     class CalledProcessError(Exception):
    63         """This exception is raised when a process run by check_call() returns
    64         a non-zero exit status. The exit status will be stored in the
    65         returncode attribute."""
    66         def __init__(self, returncode, cmd):
    67             self.returncode = returncode
    68             self.cmd = cmd
    69         def __str__(self):
    70             return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
    72 mswindows = (sys.platform == "win32")
    74 if mswindows:
    75     import winprocess
    76 else:
    77     import signal
    79 # This is normally defined in win32con, but we don't want
    80 # to incur the huge tree of dependencies (pywin32 and friends)
    81 # just to get one constant.  So here's our hack
    82 STILL_ACTIVE = 259
    84 def call(*args, **kwargs):
    85     waitargs = {}
    86     if "timeout" in kwargs:
    87         waitargs["timeout"] = kwargs.pop("timeout")
    89     return Popen(*args, **kwargs).wait(**waitargs)
    91 def check_call(*args, **kwargs):
    92     """Call a program with an optional timeout. If the program has a non-zero
    93     exit status, raises a CalledProcessError."""
    95     retcode = call(*args, **kwargs)
    96     if retcode:
    97         cmd = kwargs.get("args")
    98         if cmd is None:
    99             cmd = args[0]
   100         raise CalledProcessError(retcode, cmd)
   102 if not mswindows:
   103     def DoNothing(*args):
   104         pass
   106 class Popen(subprocess.Popen):
   107     kill_called = False
   108     if mswindows:
   109         def _execute_child(self, *args_tuple):
   110             # workaround for bug 958609
   111             if sys.hexversion < 0x02070600: # prior to 2.7.6
   112                 (args, executable, preexec_fn, close_fds,
   113                     cwd, env, universal_newlines, startupinfo,
   114                     creationflags, shell,
   115                     p2cread, p2cwrite,
   116                     c2pread, c2pwrite,
   117                     errread, errwrite) = args_tuple
   118                 to_close = set()
   119             else: # 2.7.6 and later
   120                 (args, executable, preexec_fn, close_fds,
   121                     cwd, env, universal_newlines, startupinfo,
   122                     creationflags, shell, to_close,
   123                     p2cread, p2cwrite,
   124                     c2pread, c2pwrite,
   125                     errread, errwrite) = args_tuple
   127             if not isinstance(args, types.StringTypes):
   128                 args = subprocess.list2cmdline(args)
   130             # Always or in the create new process group
   131             creationflags |= winprocess.CREATE_NEW_PROCESS_GROUP
   133             if startupinfo is None:
   134                 startupinfo = winprocess.STARTUPINFO()
   136             if None not in (p2cread, c2pwrite, errwrite):
   137                 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
   139                 startupinfo.hStdInput = int(p2cread)
   140                 startupinfo.hStdOutput = int(c2pwrite)
   141                 startupinfo.hStdError = int(errwrite)
   142             if shell:
   143                 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
   144                 startupinfo.wShowWindow = winprocess.SW_HIDE
   145                 comspec = os.environ.get("COMSPEC", "cmd.exe")
   146                 args = comspec + " /c " + args
   148             # determine if we can create create a job
   149             canCreateJob = winprocess.CanCreateJobObject()
   151             # set process creation flags
   152             creationflags |= winprocess.CREATE_SUSPENDED
   153             creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
   154             if canCreateJob:
   155                 # Uncomment this line below to discover very useful things about your environment
   156                 #print "++++ killableprocess: releng twistd patch not applied, we can create job objects"
   157                 creationflags |= winprocess.CREATE_BREAKAWAY_FROM_JOB
   159             # create the process
   160             hp, ht, pid, tid = winprocess.CreateProcess(
   161                 executable, args,
   162                 None, None, # No special security
   163                 1, # Must inherit handles!
   164                 creationflags,
   165                 winprocess.EnvironmentBlock(env),
   166                 cwd, startupinfo)
   167             self._child_created = True
   168             self._handle = hp
   169             self._thread = ht
   170             self.pid = pid
   171             self.tid = tid
   173             if canCreateJob:
   174                 # We create a new job for this process, so that we can kill
   175                 # the process and any sub-processes 
   176                 self._job = winprocess.CreateJobObject()
   177                 winprocess.AssignProcessToJobObject(self._job, int(hp))
   178             else:
   179                 self._job = None
   181             winprocess.ResumeThread(int(ht))
   182             ht.Close()
   184             if p2cread is not None:
   185                 p2cread.Close()
   186             if c2pwrite is not None:
   187                 c2pwrite.Close()
   188             if errwrite is not None:
   189                 errwrite.Close()
   190             time.sleep(.1)
   192     def kill(self, group=True):
   193         """Kill the process. If group=True, all sub-processes will also be killed."""
   194         self.kill_called = True
   196         if mswindows:
   197             if group and self._job:
   198                 winprocess.TerminateJobObject(self._job, 127)
   199             else:
   200                 winprocess.TerminateProcess(self._handle, 127)
   201             self.returncode = 127    
   202         else:
   203             if group:
   204                 try:
   205                     os.killpg(self.pid, signal.SIGKILL)
   206                 except: pass
   207             else:
   208                 os.kill(self.pid, signal.SIGKILL)
   209             self.returncode = -9
   211     def wait(self, timeout=None, group=True):
   212         """Wait for the process to terminate. Returns returncode attribute.
   213         If timeout seconds are reached and the process has not terminated,
   214         it will be forcefully killed. If timeout is -1, wait will not
   215         time out."""
   216         if timeout is not None:
   217             # timeout is now in milliseconds
   218             timeout = timeout * 1000
   220         starttime = datetime.datetime.now()
   222         if mswindows:
   223             if timeout is None:
   224                 timeout = -1
   225             rc = winprocess.WaitForSingleObject(self._handle, timeout)
   227             if (rc == winprocess.WAIT_OBJECT_0 or
   228                 rc == winprocess.WAIT_ABANDONED or
   229                 rc == winprocess.WAIT_FAILED):
   230                 # Object has either signaled, or the API call has failed.  In 
   231                 # both cases we want to give the OS the benefit of the doubt
   232                 # and supply a little time before we start shooting processes
   233                 # with an M-16.
   235                 # Returns 1 if running, 0 if not, -1 if timed out                
   236                 def check():
   237                     now = datetime.datetime.now()
   238                     diff = now - starttime
   239                     if (diff.seconds * 1000 * 1000 + diff.microseconds) < (timeout * 1000):
   240                         if self._job:
   241                             if (winprocess.QueryInformationJobObject(self._job, 8)['BasicInfo']['ActiveProcesses'] > 0):
   242                                 # Job Object is still containing active processes
   243                                 return 1
   244                         else:
   245                             # No job, we use GetExitCodeProcess, which will tell us if the process is still active
   246                             self.returncode = winprocess.GetExitCodeProcess(self._handle)
   247                             if (self.returncode == STILL_ACTIVE):
   248                                 # Process still active, continue waiting
   249                                 return 1
   250                         # Process not active, return 0
   251                         return 0
   252                     else:
   253                         # Timed out, return -1
   254                         return -1
   256                 notdone = check()
   257                 while notdone == 1:
   258                     time.sleep(.5)
   259                     notdone = check()
   261                 if notdone == -1:
   262                     # Then check timed out, we have a hung process, attempt
   263                     # last ditch kill with explosives
   264                     self.kill(group)
   266             else:
   267                 # In this case waitforsingleobject timed out.  We have to
   268                 # take the process behind the woodshed and shoot it.
   269                 self.kill(group)
   271         else:
   272             if sys.platform in ('linux2', 'sunos5', 'solaris') \
   273                     or sys.platform.startswith('freebsd'):
   274                 def group_wait(timeout):
   275                     try:
   276                         os.waitpid(self.pid, 0)
   277                     except OSError, e:
   278                         pass # If wait has already been called on this pid, bad things happen
   279                     return self.returncode
   280             elif sys.platform == 'darwin':
   281                 def group_wait(timeout):
   282                     try:
   283                         count = 0
   284                         if timeout is None and self.kill_called:
   285                             timeout = 10 # Have to set some kind of timeout or else this could go on forever
   286                         if timeout is None:
   287                             while 1:
   288                                 os.killpg(self.pid, signal.SIG_DFL)
   289                         while ((count * 2) <= timeout):
   290                             os.killpg(self.pid, signal.SIG_DFL)
   291                             # count is increased by 500ms for every 0.5s of sleep
   292                             time.sleep(.5); count += 500
   293                     except exceptions.OSError:
   294                         return self.returncode
   296             if timeout is None:
   297                 if group is True:
   298                     return group_wait(timeout)
   299                 else:
   300                     subprocess.Popen.wait(self)
   301                     return self.returncode
   303             returncode = False
   305             now = datetime.datetime.now()
   306             diff = now - starttime
   307             while (diff.seconds * 1000 * 1000 + diff.microseconds) < (timeout * 1000) and ( returncode is False ):
   308                 if group is True:
   309                     return group_wait(timeout)
   310                 else:
   311                     if subprocess.poll() is not None:
   312                         returncode = self.returncode
   313                 time.sleep(.5)
   314                 now = datetime.datetime.now()
   315                 diff = now - starttime
   316             return self.returncode
   318         return self.returncode
   319     # We get random maxint errors from subprocesses __del__
   320     __del__ = lambda self: None        
   322 def setpgid_preexec_fn():
   323     os.setpgid(0, 0)
   325 def runCommand(cmd, **kwargs):
   326     if sys.platform != "win32":
   327         return Popen(cmd, preexec_fn=setpgid_preexec_fn, **kwargs)
   328     else:
   329         return Popen(cmd, **kwargs)

mercurial