##// END OF EJS Templates
Try to catter for exception in corner cases with some version of windows...
gvaroquaux -
Show More
@@ -1,168 +1,182 b''
1 # Addapted from killableprocess.py.
1 # Addapted from killableprocess.py.
2 #______________________________________________________________________________
2 #______________________________________________________________________________
3 #
3 #
4 # killableprocess - subprocesses which can be reliably killed
4 # killableprocess - subprocesses which can be reliably killed
5 #
5 #
6 # Parts of this module are copied from the subprocess.py file contained
6 # Parts of this module are copied from the subprocess.py file contained
7 # in the Python distribution.
7 # in the Python distribution.
8 #
8 #
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
9 # Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
10 #
10 #
11 # Additions and modifications written by Benjamin Smedberg
11 # Additions and modifications written by Benjamin Smedberg
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
12 # <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
13 # <http://www.mozilla.org/>
13 # <http://www.mozilla.org/>
14 #
14 #
15 # By obtaining, using, and/or copying this software and/or its
15 # By obtaining, using, and/or copying this software and/or its
16 # associated documentation, you agree that you have read, understood,
16 # associated documentation, you agree that you have read, understood,
17 # and will comply with the following terms and conditions:
17 # and will comply with the following terms and conditions:
18 #
18 #
19 # Permission to use, copy, modify, and distribute this software and
19 # Permission to use, copy, modify, and distribute this software and
20 # its associated documentation for any purpose and without fee is
20 # its associated documentation for any purpose and without fee is
21 # hereby granted, provided that the above copyright notice appears in
21 # hereby granted, provided that the above copyright notice appears in
22 # all copies, and that both that copyright notice and this permission
22 # all copies, and that both that copyright notice and this permission
23 # notice appear in supporting documentation, and that the name of the
23 # notice appear in supporting documentation, and that the name of the
24 # author not be used in advertising or publicity pertaining to
24 # author not be used in advertising or publicity pertaining to
25 # distribution of the software without specific, written prior
25 # distribution of the software without specific, written prior
26 # permission.
26 # permission.
27 #
27 #
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
28 # THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
29 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
30 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
31 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
32 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
33 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
34 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
35
35
36 r"""killableprocess - Subprocesses which can be reliably killed
36 r"""killableprocess - Subprocesses which can be reliably killed
37
37
38 This module is a subclass of the builtin "subprocess" module. It allows
38 This module is a subclass of the builtin "subprocess" module. It allows
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
39 processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
40
40
41 It also adds a timeout argument to Wait() for a limited period of time before
41 It also adds a timeout argument to Wait() for a limited period of time before
42 forcefully killing the process.
42 forcefully killing the process.
43
43
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
44 Note: On Windows, this module requires Windows 2000 or higher (no support for
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
45 Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
46 Python 2.5+ or available from http://python.net/crew/theller/ctypes/
47 """
47 """
48
48
49 import subprocess
49 import subprocess
50 from subprocess import PIPE
50 from subprocess import PIPE
51 import sys
51 import sys
52 import os
52 import os
53 import time
54 import types
53 import types
55
54
56 try:
55 try:
57 from subprocess import CalledProcessError
56 from subprocess import CalledProcessError
58 except ImportError:
57 except ImportError:
59 # Python 2.4 doesn't implement CalledProcessError
58 # Python 2.4 doesn't implement CalledProcessError
60 class CalledProcessError(Exception):
59 class CalledProcessError(Exception):
61 """This exception is raised when a process run by check_call() returns
60 """This exception is raised when a process run by check_call() returns
62 a non-zero exit status. The exit status will be stored in the
61 a non-zero exit status. The exit status will be stored in the
63 returncode attribute."""
62 returncode attribute."""
64 def __init__(self, returncode, cmd):
63 def __init__(self, returncode, cmd):
65 self.returncode = returncode
64 self.returncode = returncode
66 self.cmd = cmd
65 self.cmd = cmd
67 def __str__(self):
66 def __str__(self):
68 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
67 return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
69
68
70 mswindows = (sys.platform == "win32")
69 mswindows = (sys.platform == "win32")
71
70
71 skip = False
72
72 if mswindows:
73 if mswindows:
73 import winprocess
74 # Some versions of windows, in some strange corner cases, give
75 # errror with the killableprocess.
76 if sys.version_info[:2] > (2, 4):
77 import platform
78 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
79 skip = True
80 else:
81 import winprocess
82 else:
83 skip = True
74 else:
84 else:
75 import signal
85 import signal
76
86
77 if not mswindows:
87 if not mswindows:
78 def DoNothing(*args):
88 def DoNothing(*args):
79 pass
89 pass
80
90
81 class Popen(subprocess.Popen):
91
92 if skip:
93 Popen = subprocess.Popen
94 else:
95 class Popen(subprocess.Popen):
82 if not mswindows:
96 if not mswindows:
83 # Override __init__ to set a preexec_fn
97 # Override __init__ to set a preexec_fn
84 def __init__(self, *args, **kwargs):
98 def __init__(self, *args, **kwargs):
85 if len(args) >= 7:
99 if len(args) >= 7:
86 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
100 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
87
101
88 real_preexec_fn = kwargs.pop("preexec_fn", None)
102 real_preexec_fn = kwargs.pop("preexec_fn", None)
89 def setpgid_preexec_fn():
103 def setpgid_preexec_fn():
90 os.setpgid(0, 0)
104 os.setpgid(0, 0)
91 if real_preexec_fn:
105 if real_preexec_fn:
92 apply(real_preexec_fn)
106 apply(real_preexec_fn)
93
107
94 kwargs['preexec_fn'] = setpgid_preexec_fn
108 kwargs['preexec_fn'] = setpgid_preexec_fn
95
109
96 subprocess.Popen.__init__(self, *args, **kwargs)
110 subprocess.Popen.__init__(self, *args, **kwargs)
97
111
98 if mswindows:
112 if mswindows:
99 def _execute_child(self, args, executable, preexec_fn, close_fds,
113 def _execute_child(self, args, executable, preexec_fn, close_fds,
100 cwd, env, universal_newlines, startupinfo,
114 cwd, env, universal_newlines, startupinfo,
101 creationflags, shell,
115 creationflags, shell,
102 p2cread, p2cwrite,
116 p2cread, p2cwrite,
103 c2pread, c2pwrite,
117 c2pread, c2pwrite,
104 errread, errwrite):
118 errread, errwrite):
105 if not isinstance(args, types.StringTypes):
119 if not isinstance(args, types.StringTypes):
106 args = subprocess.list2cmdline(args)
120 args = subprocess.list2cmdline(args)
107
121
108 if startupinfo is None:
122 if startupinfo is None:
109 startupinfo = winprocess.STARTUPINFO()
123 startupinfo = winprocess.STARTUPINFO()
110
124
111 if None not in (p2cread, c2pwrite, errwrite):
125 if None not in (p2cread, c2pwrite, errwrite):
112 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
126 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
113
127
114 startupinfo.hStdInput = int(p2cread)
128 startupinfo.hStdInput = int(p2cread)
115 startupinfo.hStdOutput = int(c2pwrite)
129 startupinfo.hStdOutput = int(c2pwrite)
116 startupinfo.hStdError = int(errwrite)
130 startupinfo.hStdError = int(errwrite)
117 if shell:
131 if shell:
118 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
132 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
119 startupinfo.wShowWindow = winprocess.SW_HIDE
133 startupinfo.wShowWindow = winprocess.SW_HIDE
120 comspec = os.environ.get("COMSPEC", "cmd.exe")
134 comspec = os.environ.get("COMSPEC", "cmd.exe")
121 args = comspec + " /c " + args
135 args = comspec + " /c " + args
122
136
123 # We create a new job for this process, so that we can kill
137 # We create a new job for this process, so that we can kill
124 # the process and any sub-processes
138 # the process and any sub-processes
125 self._job = winprocess.CreateJobObject()
139 self._job = winprocess.CreateJobObject()
126
140
127 creationflags |= winprocess.CREATE_SUSPENDED
141 creationflags |= winprocess.CREATE_SUSPENDED
128 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
142 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
129
143
130 hp, ht, pid, tid = winprocess.CreateProcess(
144 hp, ht, pid, tid = winprocess.CreateProcess(
131 executable, args,
145 executable, args,
132 None, None, # No special security
146 None, None, # No special security
133 1, # Must inherit handles!
147 1, # Must inherit handles!
134 creationflags,
148 creationflags,
135 winprocess.EnvironmentBlock(env),
149 winprocess.EnvironmentBlock(env),
136 cwd, startupinfo)
150 cwd, startupinfo)
137
151
138 self._child_created = True
152 self._child_created = True
139 self._handle = hp
153 self._handle = hp
140 self._thread = ht
154 self._thread = ht
141 self.pid = pid
155 self.pid = pid
142
156
143 winprocess.AssignProcessToJobObject(self._job, hp)
157 winprocess.AssignProcessToJobObject(self._job, hp)
144 winprocess.ResumeThread(ht)
158 winprocess.ResumeThread(ht)
145
159
146 if p2cread is not None:
160 if p2cread is not None:
147 p2cread.Close()
161 p2cread.Close()
148 if c2pwrite is not None:
162 if c2pwrite is not None:
149 c2pwrite.Close()
163 c2pwrite.Close()
150 if errwrite is not None:
164 if errwrite is not None:
151 errwrite.Close()
165 errwrite.Close()
152
166
153 def kill(self, group=True):
167 def kill(self, group=True):
154 """Kill the process. If group=True, all sub-processes will also be killed."""
168 """Kill the process. If group=True, all sub-processes will also be killed."""
155 if mswindows:
169 if mswindows:
156 if group:
170 if group:
157 winprocess.TerminateJobObject(self._job, 127)
171 winprocess.TerminateJobObject(self._job, 127)
158 else:
172 else:
159 winprocess.TerminateProcess(self._handle, 127)
173 winprocess.TerminateProcess(self._handle, 127)
160 self.returncode = 127
174 self.returncode = 127
161 else:
175 else:
162 if group:
176 if group:
163 os.killpg(self.pid, signal.SIGKILL)
177 os.killpg(self.pid, signal.SIGKILL)
164 else:
178 else:
165 os.kill(self.pid, signal.SIGKILL)
179 os.kill(self.pid, signal.SIGKILL)
166 self.returncode = -9
180 self.returncode = -9
167
181
168
182
General Comments 0
You need to be logged in to leave comments. Login now