##// END OF EJS Templates
Fix vista bug: subprocess execution not working if not started from...
gvaroquaux -
Show More
@@ -1,177 +1,179 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 types
53 import types
54
54
55 try:
55 try:
56 from subprocess import CalledProcessError
56 from subprocess import CalledProcessError
57 except ImportError:
57 except ImportError:
58 # Python 2.4 doesn't implement CalledProcessError
58 # Python 2.4 doesn't implement CalledProcessError
59 class CalledProcessError(Exception):
59 class CalledProcessError(Exception):
60 """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
61 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
62 returncode attribute."""
62 returncode attribute."""
63 def __init__(self, returncode, cmd):
63 def __init__(self, returncode, cmd):
64 self.returncode = returncode
64 self.returncode = returncode
65 self.cmd = cmd
65 self.cmd = cmd
66 def __str__(self):
66 def __str__(self):
67 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)
68
68
69 mswindows = (sys.platform == "win32")
69 mswindows = (sys.platform == "win32")
70
70
71 skip = False
71 skip = False
72
72
73 if mswindows:
73 if mswindows:
74 import platform
74 import platform
75 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
75 if platform.uname()[3] == '' or platform.uname()[3] > '6.0.6000':
76 # Killable process does not work under vista when starting for
77 # something else than cmd.
76 skip = True
78 skip = True
77 else:
79 else:
78 import winprocess
80 import winprocess
79 else:
81 else:
80 import signal
82 import signal
81
83
82 if not mswindows:
84 if not mswindows:
83 def DoNothing(*args):
85 def DoNothing(*args):
84 pass
86 pass
85
87
86
88
87 if skip:
89 if skip:
88 Popen = subprocess.Popen
90 Popen = subprocess.Popen
89 else:
91 else:
90 class Popen(subprocess.Popen):
92 class Popen(subprocess.Popen):
91 if not mswindows:
93 if not mswindows:
92 # Override __init__ to set a preexec_fn
94 # Override __init__ to set a preexec_fn
93 def __init__(self, *args, **kwargs):
95 def __init__(self, *args, **kwargs):
94 if len(args) >= 7:
96 if len(args) >= 7:
95 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
97 raise Exception("Arguments preexec_fn and after must be passed by keyword.")
96
98
97 real_preexec_fn = kwargs.pop("preexec_fn", None)
99 real_preexec_fn = kwargs.pop("preexec_fn", None)
98 def setpgid_preexec_fn():
100 def setpgid_preexec_fn():
99 os.setpgid(0, 0)
101 os.setpgid(0, 0)
100 if real_preexec_fn:
102 if real_preexec_fn:
101 apply(real_preexec_fn)
103 apply(real_preexec_fn)
102
104
103 kwargs['preexec_fn'] = setpgid_preexec_fn
105 kwargs['preexec_fn'] = setpgid_preexec_fn
104
106
105 subprocess.Popen.__init__(self, *args, **kwargs)
107 subprocess.Popen.__init__(self, *args, **kwargs)
106
108
107 if mswindows:
109 if mswindows:
108 def _execute_child(self, args, executable, preexec_fn, close_fds,
110 def _execute_child(self, args, executable, preexec_fn, close_fds,
109 cwd, env, universal_newlines, startupinfo,
111 cwd, env, universal_newlines, startupinfo,
110 creationflags, shell,
112 creationflags, shell,
111 p2cread, p2cwrite,
113 p2cread, p2cwrite,
112 c2pread, c2pwrite,
114 c2pread, c2pwrite,
113 errread, errwrite):
115 errread, errwrite):
114 if not isinstance(args, types.StringTypes):
116 if not isinstance(args, types.StringTypes):
115 args = subprocess.list2cmdline(args)
117 args = subprocess.list2cmdline(args)
116
118
117 if startupinfo is None:
119 if startupinfo is None:
118 startupinfo = winprocess.STARTUPINFO()
120 startupinfo = winprocess.STARTUPINFO()
119
121
120 if None not in (p2cread, c2pwrite, errwrite):
122 if None not in (p2cread, c2pwrite, errwrite):
121 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
123 startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
122
124
123 startupinfo.hStdInput = int(p2cread)
125 startupinfo.hStdInput = int(p2cread)
124 startupinfo.hStdOutput = int(c2pwrite)
126 startupinfo.hStdOutput = int(c2pwrite)
125 startupinfo.hStdError = int(errwrite)
127 startupinfo.hStdError = int(errwrite)
126 if shell:
128 if shell:
127 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
129 startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
128 startupinfo.wShowWindow = winprocess.SW_HIDE
130 startupinfo.wShowWindow = winprocess.SW_HIDE
129 comspec = os.environ.get("COMSPEC", "cmd.exe")
131 comspec = os.environ.get("COMSPEC", "cmd.exe")
130 args = comspec + " /c " + args
132 args = comspec + " /c " + args
131
133
132 # We create a new job for this process, so that we can kill
134 # We create a new job for this process, so that we can kill
133 # the process and any sub-processes
135 # the process and any sub-processes
134 self._job = winprocess.CreateJobObject()
136 self._job = winprocess.CreateJobObject()
135
137
136 creationflags |= winprocess.CREATE_SUSPENDED
138 creationflags |= winprocess.CREATE_SUSPENDED
137 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
139 creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
138
140
139 hp, ht, pid, tid = winprocess.CreateProcess(
141 hp, ht, pid, tid = winprocess.CreateProcess(
140 executable, args,
142 executable, args,
141 None, None, # No special security
143 None, None, # No special security
142 1, # Must inherit handles!
144 1, # Must inherit handles!
143 creationflags,
145 creationflags,
144 winprocess.EnvironmentBlock(env),
146 winprocess.EnvironmentBlock(env),
145 cwd, startupinfo)
147 cwd, startupinfo)
146
148
147 self._child_created = True
149 self._child_created = True
148 self._handle = hp
150 self._handle = hp
149 self._thread = ht
151 self._thread = ht
150 self.pid = pid
152 self.pid = pid
151
153
152 winprocess.AssignProcessToJobObject(self._job, hp)
154 winprocess.AssignProcessToJobObject(self._job, hp)
153 winprocess.ResumeThread(ht)
155 winprocess.ResumeThread(ht)
154
156
155 if p2cread is not None:
157 if p2cread is not None:
156 p2cread.Close()
158 p2cread.Close()
157 if c2pwrite is not None:
159 if c2pwrite is not None:
158 c2pwrite.Close()
160 c2pwrite.Close()
159 if errwrite is not None:
161 if errwrite is not None:
160 errwrite.Close()
162 errwrite.Close()
161
163
162 def kill(self, group=True):
164 def kill(self, group=True):
163 """Kill the process. If group=True, all sub-processes will also be killed."""
165 """Kill the process. If group=True, all sub-processes will also be killed."""
164 if mswindows:
166 if mswindows:
165 if group:
167 if group:
166 winprocess.TerminateJobObject(self._job, 127)
168 winprocess.TerminateJobObject(self._job, 127)
167 else:
169 else:
168 winprocess.TerminateProcess(self._handle, 127)
170 winprocess.TerminateProcess(self._handle, 127)
169 self.returncode = 127
171 self.returncode = 127
170 else:
172 else:
171 if group:
173 if group:
172 os.killpg(self.pid, signal.SIGKILL)
174 os.killpg(self.pid, signal.SIGKILL)
173 else:
175 else:
174 os.kill(self.pid, signal.SIGKILL)
176 os.kill(self.pid, signal.SIGKILL)
175 self.returncode = -9
177 self.returncode = -9
176
178
177
179
General Comments 0
You need to be logged in to leave comments. Login now