Commit 1d9f6294 authored by maruel@chromium.org's avatar maruel@chromium.org

Set returncode to subprocess2.TIMED_OUT instead of -9 when a process times out.

R=dpranke@chromium.org
BUG=
TEST=

Review URL: http://codereview.chromium.org/6806009

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@80775 0039d316-1c4b-4281-b951-d872f2087c98
parent d25fb8f6
...@@ -8,6 +8,7 @@ In theory you shouldn't need anything else in subprocess, or this module failed. ...@@ -8,6 +8,7 @@ In theory you shouldn't need anything else in subprocess, or this module failed.
""" """
from __future__ import with_statement from __future__ import with_statement
import errno
import logging import logging
import os import os
import subprocess import subprocess
...@@ -21,7 +22,8 @@ PIPE = subprocess.PIPE ...@@ -21,7 +22,8 @@ PIPE = subprocess.PIPE
STDOUT = subprocess.STDOUT STDOUT = subprocess.STDOUT
# Sends stdout or stderr to os.devnull. # Sends stdout or stderr to os.devnull.
VOID = '/dev/null' VOID = '/dev/null'
# Error code when a process was killed because it timed out.
TIMED_OUT = -2001
# Globals. # Globals.
# Set to True if you somehow need to disable this hack. # Set to True if you somehow need to disable this hack.
...@@ -44,6 +46,10 @@ class CalledProcessError(subprocess.CalledProcessError): ...@@ -44,6 +46,10 @@ class CalledProcessError(subprocess.CalledProcessError):
return '\n'.join(filter(None, (out, self.stdout, self.stderr))) return '\n'.join(filter(None, (out, self.stdout, self.stderr)))
class CygwinRebaseError(CalledProcessError):
"""Occurs when cygwin's fork() emulation fails due to rebased dll."""
## Utility functions ## Utility functions
...@@ -156,12 +162,34 @@ def Popen(args, **kwargs): ...@@ -156,12 +162,34 @@ def Popen(args, **kwargs):
tmp_str += '; cwd=%s' % kwargs['cwd'] tmp_str += '; cwd=%s' % kwargs['cwd']
logging.debug(tmp_str) logging.debug(tmp_str)
# Replaces VOID with handle to /dev/null. def fix(stream):
if kwargs.get('stdout') in (VOID, os.devnull): if kwargs.get(stream) in (VOID, os.devnull):
kwargs['stdout'] = open(os.devnull, 'w') # Replaces VOID with handle to /dev/null.
if kwargs.get('stderr') in (VOID, os.devnull): # Create a temporary file to workaround python's deadlock.
kwargs['stderr'] = open(os.devnull, 'w') # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
return subprocess.Popen(args, **kwargs) # When the pipe fills up, it will deadlock this process. Using a real file
# works around that issue.
kwargs[stream] = open(os.devnull, 'w')
fix('stdout')
fix('stderr')
try:
return subprocess.Popen(args, **kwargs)
except OSError, e:
if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
# Convert fork() emulation failure into a CygwinRebaseError().
raise CygwinRebaseError(
e.errno,
args,
kwargs.get('cwd'),
None,
'Visit '
'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to '
'learn how to fix this error; you need to rebase your cygwin dlls')
# Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
# through
raise
def call(args, timeout=None, **kwargs): def call(args, timeout=None, **kwargs):
...@@ -169,7 +197,8 @@ def call(args, timeout=None, **kwargs): ...@@ -169,7 +197,8 @@ def call(args, timeout=None, **kwargs):
Returns ((stdout, stderr), returncode). Returns ((stdout, stderr), returncode).
- The process will be kill with error code -9 after |timeout| seconds if set. - The process will be killed after |timeout| seconds and returncode set to
TIMED_OUT.
- Automatically passes stdin content as input so do not specify stdin=PIPE. - Automatically passes stdin content as input so do not specify stdin=PIPE.
""" """
stdin = kwargs.pop('stdin', None) stdin = kwargs.pop('stdin', None)
...@@ -183,34 +212,31 @@ def call(args, timeout=None, **kwargs): ...@@ -183,34 +212,31 @@ def call(args, timeout=None, **kwargs):
# Normal workflow. # Normal workflow.
proc = Popen(args, **kwargs) proc = Popen(args, **kwargs)
if stdin is not None: if stdin is not None:
out = proc.communicate(stdin) return proc.communicate(stdin), proc.returncode
else: else:
out = proc.communicate() return proc.communicate(), proc.returncode
else:
# Create a temporary file to workaround python's deadlock. # Create a temporary file to workaround python's deadlock.
# http://docs.python.org/library/subprocess.html#subprocess.Popen.wait # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
# When the pipe fills up, it will deadlock this process. Using a real file # When the pipe fills up, it will deadlock this process. Using a real file
# works around that issue. # works around that issue.
with tempfile.TemporaryFile() as buff: with tempfile.TemporaryFile() as buff:
start = time.time() start = time.time()
kwargs['stdout'] = buff kwargs['stdout'] = buff
proc = Popen(args, **kwargs) proc = Popen(args, **kwargs)
if stdin is not None: if stdin is not None:
proc.stdin.write(stdin) proc.stdin.write(stdin)
while proc.returncode is None: while proc.returncode is None:
proc.poll() proc.poll()
if timeout and (time.time() - start) > timeout: if timeout and (time.time() - start) > timeout:
proc.kill() proc.kill()
proc.wait() proc.wait()
# It's -9 on linux and 1 on Windows. Standardize to -9. # It's -9 on linux and 1 on Windows. Standardize to TIMED_OUT.
# Do not throw an exception here, the user must use proc.returncode = TIMED_OUT
# check_call(timeout=60) and check for e.returncode == -9 instead. time.sleep(0.001)
# or look at call()[1] == -9. # Now that the process died, reset the cursor and read the file.
proc.returncode = -9 buff.seek(0)
time.sleep(0.001) out = [buff.read(), None]
# Now that the process died, reset the cursor and read the file.
buff.seek(0)
out = [buff.read(), None]
return out, proc.returncode return out, proc.returncode
......
...@@ -136,7 +136,7 @@ class Subprocess2Test(unittest.TestCase): ...@@ -136,7 +136,7 @@ class Subprocess2Test(unittest.TestCase):
self.exe + ['--sleep', '--stdout'], self.exe + ['--sleep', '--stdout'],
timeout=0.01, timeout=0.01,
stdout=subprocess2.PIPE) stdout=subprocess2.PIPE)
self.assertEquals(-9, returncode) self.assertEquals(subprocess2.TIMED_OUT, returncode)
self.assertEquals(['', None], out) self.assertEquals(['', None], out)
def test_void(self): def test_void(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment