1 # killableprocess - subprocesses which can be reliably killed
3 # Parts of this module are copied from the subprocess.py file contained
4 # in the Python distribution.
6 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
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/>
12 # By obtaining, using, and/or copying this software and/or its
13 # associated documentation, you agree that you have read, understood,
14 # and will comply with the following terms and conditions:
16 # Permission to use, copy, modify, and distribute this software and
17 # its associated documentation for any purpose and without fee is
18 # hereby granted, provided that the above copyright notice appears in
19 # all copies, and that both that copyright notice and this permission
20 # notice appear in supporting documentation, and that the name of the
21 # author not be used in advertising or publicity pertaining to
22 # distribution of the software without specific, written prior
25 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
26 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
27 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
28 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
29 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
30 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
31 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33 r
"""killableprocess - Subprocesses which can be reliably killed
35 This module is a subclass of the builtin "subprocess" module. It allows
36 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
38 It also adds a timeout argument to Wait() for a limited period of time before
39 forcefully killing the process.
41 Note: On Windows, this module requires Windows 2000 or higher (no support for
42 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
43 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
53 from subprocess
import CalledProcessError
55 # Python 2.4 doesn't implement CalledProcessError
56 class CalledProcessError(Exception):
57 """This exception is raised when a process run by check_call() returns
58 a non-zero exit status. The exit status will be stored in the
59 returncode attribute."""
60 def __init__(self
, returncode
, cmd
):
61 self
.returncode
= returncode
64 return "Command '%s' returned non-zero exit status %d" % (self
.cmd
, self
.returncode
)
66 mswindows
= (sys
.platform
== "win32")
73 def call(*args
, **kwargs
):
75 if "timeout" in kwargs
:
76 waitargs
["timeout"] = kwargs
.pop("timeout")
78 return Popen(*args
, **kwargs
).wait(**waitargs
)
80 def check_call(*args
, **kwargs
):
81 """Call a program with an optional timeout. If the program has a non-zero
82 exit status, raises a CalledProcessError."""
84 retcode
= call(*args
, **kwargs
)
86 cmd
= kwargs
.get("args")
89 raise CalledProcessError(retcode
, cmd
)
95 class Popen(subprocess
.Popen
):
97 # Override __init__ to set a preexec_fn
98 def __init__(self
, *args
, **kwargs
):
100 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
102 real_preexec_fn
= kwargs
.pop("preexec_fn", None)
103 def setpgid_preexec_fn():
106 apply(real_preexec_fn
)
108 kwargs
['preexec_fn'] = setpgid_preexec_fn
110 subprocess
.Popen
.__init
__(self
, *args
, **kwargs
)
113 def _execute_child(self
, args
, executable
, preexec_fn
, close_fds
,
114 cwd
, env
, universal_newlines
, startupinfo
,
115 creationflags
, shell
,
119 if not isinstance(args
, types
.StringTypes
):
120 args
= subprocess
.list2cmdline(args
)
122 if startupinfo
is None:
123 startupinfo
= winprocess
.STARTUPINFO()
125 if None not in (p2cread
, c2pwrite
, errwrite
):
126 startupinfo
.dwFlags |
= winprocess
.STARTF_USESTDHANDLES
128 startupinfo
.hStdInput
= int(p2cread
)
129 startupinfo
.hStdOutput
= int(c2pwrite
)
130 startupinfo
.hStdError
= int(errwrite
)
132 startupinfo
.dwFlags |
= winprocess
.STARTF_USESHOWWINDOW
133 # startupinfo.wShowWindow = winprocess.SW_HIDE
134 comspec
= os
.environ
.get("COMSPEC", "cmd.exe")
135 args
= comspec
+ " /c " + args
137 # We create a new job for this process, so that we can kill
138 # the process and any sub-processes
139 self
._job
= winprocess
.CreateJobObject()
141 creationflags |
= winprocess
.CREATE_SUSPENDED
142 creationflags |
= winprocess
.CREATE_UNICODE_ENVIRONMENT
144 hp
, ht
, pid
, tid
= winprocess
.CreateProcess(
146 None, None, # No special security
147 1, # Must inherit handles!
149 winprocess
.EnvironmentBlock(env
),
152 self
._child
_created
= True
157 winprocess
.AssignProcessToJobObject(self
._job
, hp
)
158 winprocess
.ResumeThread(ht
)
160 if p2cread
is not None:
162 if c2pwrite
is not None:
164 if errwrite
is not None:
167 def kill(self
, group
=True):
168 """Kill the process. If group=True, all sub-processes will also be killed."""
171 winprocess
.TerminateJobObject(self
._job
, 127)
173 winprocess
.TerminateProcess(self
._handle
, 127)
174 self
.returncode
= 127
177 os
.killpg(self
.pid
, signal
.SIGKILL
)
179 os
.kill(self
.pid
, signal
.SIGKILL
)
182 def wait(self
, timeout
=-1, group
=True):
183 """Wait for the process to terminate. Returns returncode attribute.
184 If timeout seconds are reached and the process has not terminated,
185 it will be forcefully killed. If timeout is -1, wait will not
188 if self
.returncode
is not None:
189 return self
.returncode
193 timeout
= timeout
* 1000
194 rc
= winprocess
.WaitForSingleObject(self
._handle
, timeout
)
195 if rc
== winprocess
.WAIT_TIMEOUT
:
198 self
.returncode
= winprocess
.GetExitCodeProcess(self
._handle
)
201 subprocess
.Popen
.wait(self
)
202 return self
.returncode
204 starttime
= time
.time()
206 # Make sure there is a signal handler for SIGCHLD installed
207 oldsignal
= signal
.signal(signal
.SIGCHLD
, DoNothing
)
209 while time
.time() < starttime
+ timeout
- 0.01:
210 pid
, sts
= os
.waitpid(self
.pid
, os
.WNOHANG
)
212 self
._handle
_exitstatus
(sts
)
213 signal
.signal(signal
.SIGCHLD
, oldsignal
)
214 return self
.returncode
216 # time.sleep is interrupted by signals (good!)
217 newtimeout
= timeout
- time
.time() + starttime
218 time
.sleep(newtimeout
)
221 signal
.signal(signal
.SIGCHLD
, oldsignal
)
222 subprocess
.Popen
.wait(self
)
224 return self
.returncode