Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(316)

Side by Side Diff: subprocess2.py

Issue 6806009: Set returncode to subprocess2.TIMED_OUT instead of -9 when a process times out. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/subprocess2_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # coding=utf8 1 # coding=utf8
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 """Collection of subprocess wrapper functions. 5 """Collection of subprocess wrapper functions.
6 6
7 In theory you shouldn't need anything else in subprocess, or this module failed. 7 In theory you shouldn't need anything else in subprocess, or this module failed.
8 """ 8 """
9 9
10 from __future__ import with_statement 10 from __future__ import with_statement
11 import errno
11 import logging 12 import logging
12 import os 13 import os
13 import subprocess 14 import subprocess
14 import sys 15 import sys
15 import tempfile 16 import tempfile
16 import time 17 import time
17 import threading 18 import threading
18 19
19 # Constants forwarded from subprocess. 20 # Constants forwarded from subprocess.
20 PIPE = subprocess.PIPE 21 PIPE = subprocess.PIPE
21 STDOUT = subprocess.STDOUT 22 STDOUT = subprocess.STDOUT
22 # Sends stdout or stderr to os.devnull. 23 # Sends stdout or stderr to os.devnull.
23 VOID = '/dev/null' 24 VOID = '/dev/null'
24 25 # Error code when a process was killed because it timed out.
26 TIMED_OUT = -2001
Dirk Pranke 2011/04/07 02:03:46 Where did -2001 come from?
25 27
26 # Globals. 28 # Globals.
27 # Set to True if you somehow need to disable this hack. 29 # Set to True if you somehow need to disable this hack.
28 SUBPROCESS_CLEANUP_HACKED = False 30 SUBPROCESS_CLEANUP_HACKED = False
29 31
30 32
31 class CalledProcessError(subprocess.CalledProcessError): 33 class CalledProcessError(subprocess.CalledProcessError):
32 """Augment the standard exception with more data.""" 34 """Augment the standard exception with more data."""
33 def __init__(self, returncode, cmd, cwd, stdout, stderr): 35 def __init__(self, returncode, cmd, cwd, stdout, stderr):
34 super(CalledProcessError, self).__init__(returncode, cmd) 36 super(CalledProcessError, self).__init__(returncode, cmd)
35 self.stdout = stdout 37 self.stdout = stdout
36 self.stderr = stderr 38 self.stderr = stderr
37 self.cwd = cwd 39 self.cwd = cwd
38 40
39 def __str__(self): 41 def __str__(self):
40 out = 'Command %s returned non-zero exit status %s' % ( 42 out = 'Command %s returned non-zero exit status %s' % (
41 ' '.join(self.cmd), self.returncode) 43 ' '.join(self.cmd), self.returncode)
42 if self.cwd: 44 if self.cwd:
43 out += ' in ' + self.cwd 45 out += ' in ' + self.cwd
44 return '\n'.join(filter(None, (out, self.stdout, self.stderr))) 46 return '\n'.join(filter(None, (out, self.stdout, self.stderr)))
45 47
46 48
49 class CygwinRebaseError(CalledProcessError):
50 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
51
52
47 ## Utility functions 53 ## Utility functions
48 54
49 55
50 def kill_pid(pid): 56 def kill_pid(pid):
51 """Kills a process by its process id.""" 57 """Kills a process by its process id."""
52 try: 58 try:
53 # Unable to import 'module' 59 # Unable to import 'module'
54 # pylint: disable=E1101,F0401 60 # pylint: disable=E1101,F0401
55 import signal 61 import signal
56 return os.kill(pid, signal.SIGKILL) 62 return os.kill(pid, signal.SIGKILL)
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
149 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the 155 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for the
150 # executable, but shell=True makes subprocess on Linux fail when it's called 156 # executable, but shell=True makes subprocess on Linux fail when it's called
151 # with a list because it only tries to execute the first item in the list. 157 # with a list because it only tries to execute the first item in the list.
152 kwargs['shell'] = bool(sys.platform=='win32') 158 kwargs['shell'] = bool(sys.platform=='win32')
153 159
154 tmp_str = ' '.join(args) 160 tmp_str = ' '.join(args)
155 if kwargs.get('cwd', None): 161 if kwargs.get('cwd', None):
156 tmp_str += '; cwd=%s' % kwargs['cwd'] 162 tmp_str += '; cwd=%s' % kwargs['cwd']
157 logging.debug(tmp_str) 163 logging.debug(tmp_str)
158 164
159 # Replaces VOID with handle to /dev/null. 165 def fix(stream):
160 if kwargs.get('stdout') in (VOID, os.devnull): 166 if kwargs.get(stream) in (VOID, os.devnull):
161 kwargs['stdout'] = open(os.devnull, 'w') 167 # Replaces VOID with handle to /dev/null.
162 if kwargs.get('stderr') in (VOID, os.devnull): 168 # Create a temporary file to workaround python's deadlock.
163 kwargs['stderr'] = open(os.devnull, 'w') 169 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
164 return subprocess.Popen(args, **kwargs) 170 # When the pipe fills up, it will deadlock this process. Using a real file
171 # works around that issue.
172 kwargs[stream] = open(os.devnull, 'w')
173
174 fix('stdout')
175 fix('stderr')
176
177 try:
178 return subprocess.Popen(args, **kwargs)
179 except OSError, e:
180 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
181 # Convert fork() emulation failure into a CygwinRebaseError().
182 raise CygwinRebaseError(
183 e.errno,
184 args,
185 kwargs.get('cwd'),
186 None,
187 'Visit '
188 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to '
189 'learn how to fix this error; you need to rebase your cygwin dlls')
190 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
191 # through
192 raise
165 193
166 194
167 def call(args, timeout=None, **kwargs): 195 def call(args, timeout=None, **kwargs):
168 """Wraps subprocess.Popen().communicate(). 196 """Wraps subprocess.Popen().communicate().
169 197
170 Returns ((stdout, stderr), returncode). 198 Returns ((stdout, stderr), returncode).
171 199
172 - The process will be kill with error code -9 after |timeout| seconds if set. 200 - The process will be kill with error code TIMED_OUT after |timeout| seconds
Dirk Pranke 2011/04/07 02:03:46 You're not actually killing with an error code; I
201 if set.
173 - Automatically passes stdin content as input so do not specify stdin=PIPE. 202 - Automatically passes stdin content as input so do not specify stdin=PIPE.
174 """ 203 """
175 stdin = kwargs.pop('stdin', None) 204 stdin = kwargs.pop('stdin', None)
176 if stdin is not None: 205 if stdin is not None:
177 assert stdin != PIPE 206 assert stdin != PIPE
178 # When stdin is passed as an argument, use it as the actual input data and 207 # When stdin is passed as an argument, use it as the actual input data and
179 # set the Popen() parameter accordingly. 208 # set the Popen() parameter accordingly.
180 kwargs['stdin'] = PIPE 209 kwargs['stdin'] = PIPE
181 210
182 if not timeout: 211 if not timeout:
183 # Normal workflow. 212 # Normal workflow.
184 proc = Popen(args, **kwargs) 213 proc = Popen(args, **kwargs)
185 if stdin is not None: 214 if stdin is not None:
186 out = proc.communicate(stdin) 215 return proc.communicate(stdin), proc.returncode
187 else: 216 else:
188 out = proc.communicate() 217 return proc.communicate(), proc.returncode
189 else: 218
190 # Create a temporary file to workaround python's deadlock. 219 # Create a temporary file to workaround python's deadlock.
191 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait 220 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
192 # When the pipe fills up, it will deadlock this process. Using a real file 221 # When the pipe fills up, it will deadlock this process. Using a real file
193 # works around that issue. 222 # works around that issue.
194 with tempfile.TemporaryFile() as buff: 223 with tempfile.TemporaryFile() as buff:
195 start = time.time() 224 start = time.time()
196 kwargs['stdout'] = buff 225 kwargs['stdout'] = buff
197 proc = Popen(args, **kwargs) 226 proc = Popen(args, **kwargs)
198 if stdin is not None: 227 if stdin is not None:
199 proc.stdin.write(stdin) 228 proc.stdin.write(stdin)
200 while proc.returncode is None: 229 while proc.returncode is None:
201 proc.poll() 230 proc.poll()
202 if timeout and (time.time() - start) > timeout: 231 if timeout and (time.time() - start) > timeout:
203 proc.kill() 232 proc.kill()
204 proc.wait() 233 proc.wait()
205 # It's -9 on linux and 1 on Windows. Standardize to -9. 234 # It's -9 on linux and 1 on Windows. Standardize to TIMED_OUT.
206 # Do not throw an exception here, the user must use 235 proc.returncode = TIMED_OUT
207 # check_call(timeout=60) and check for e.returncode == -9 instead. 236 time.sleep(0.001)
208 # or look at call()[1] == -9. 237 # Now that the process died, reset the cursor and read the file.
209 proc.returncode = -9 238 buff.seek(0)
210 time.sleep(0.001) 239 out = [buff.read(), None]
211 # Now that the process died, reset the cursor and read the file.
212 buff.seek(0)
213 out = [buff.read(), None]
214 return out, proc.returncode 240 return out, proc.returncode
215 241
216 242
217 def check_call(args, **kwargs): 243 def check_call(args, **kwargs):
218 """Improved version of subprocess.check_call(). 244 """Improved version of subprocess.check_call().
219 245
220 Returns (stdout, stderr), unlike subprocess.check_call(). 246 Returns (stdout, stderr), unlike subprocess.check_call().
221 """ 247 """
222 out, returncode = call(args, **kwargs) 248 out, returncode = call(args, **kwargs)
223 if returncode: 249 if returncode:
(...skipping 24 matching lines...) Expand all
248 274
249 - Discards stderr. By default sets stderr=STDOUT. 275 - Discards stderr. By default sets stderr=STDOUT.
250 - Throws if return code is not 0. 276 - Throws if return code is not 0.
251 - Works even prior to python 2.7. 277 - Works even prior to python 2.7.
252 """ 278 """
253 if kwargs.get('stdout') is None: 279 if kwargs.get('stdout') is None:
254 kwargs['stdout'] = PIPE 280 kwargs['stdout'] = PIPE
255 if kwargs.get('stderr') is None: 281 if kwargs.get('stderr') is None:
256 kwargs['stderr'] = STDOUT 282 kwargs['stderr'] = STDOUT
257 return check_call(args, **kwargs)[0] 283 return check_call(args, **kwargs)[0]
OLDNEW
« no previous file with comments | « no previous file | tests/subprocess2_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698