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

Unified Diff: build/android/pylib/cmd_helper.py

Issue 300063017: Fix silent failures in test runner that lead to long timeouts (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Unbreak windows. Created 6 years, 7 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | build/android/pylib/perf/test_runner.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: build/android/pylib/cmd_helper.py
diff --git a/build/android/pylib/cmd_helper.py b/build/android/pylib/cmd_helper.py
index 4aa17b9d10726afd14cbd14eb2227aa28894cc41..aba00be735368e9e931e321b14bef1d46ad1fbf3 100644
--- a/build/android/pylib/cmd_helper.py
+++ b/build/android/pylib/cmd_helper.py
@@ -5,12 +5,19 @@
"""A wrapper for subprocess to make calling shell commands easier."""
import logging
+import os
import pipes
+import select
import signal
+import StringIO
import subprocess
-import tempfile
+import time
-from pylib.utils import timeout_retry
+# fcntl is not available on Windows.
+try:
+ import fcntl
+except ImportError:
+ fcntl = None
def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
@@ -88,33 +95,73 @@ def GetCmdStatusAndOutput(args, cwd=None, shell=False):
s += ':' + cwd
s += '> ' + args_repr
logging.info(s)
- tmpout = tempfile.TemporaryFile(bufsize=0)
- tmperr = tempfile.TemporaryFile(bufsize=0)
- exit_code = Call(args, cwd=cwd, stdout=tmpout, stderr=tmperr, shell=shell)
- tmperr.seek(0)
- stderr = tmperr.read()
- tmperr.close()
+ pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ shell=shell, cwd=cwd)
+ stdout, stderr = pipe.communicate()
+
if stderr:
logging.critical(stderr)
- tmpout.seek(0)
- stdout = tmpout.read()
- tmpout.close()
if len(stdout) > 4096:
logging.debug('Truncated output:')
logging.debug(stdout[:4096])
- return (exit_code, stdout)
+ return (pipe.returncode, stdout)
+
+
+class TimeoutError(Exception):
+ """Module-specific timeout exception."""
+ pass
-def GetCmdStatusAndOutputWithTimeoutAndRetries(args, timeout, retries):
- """Executes a subprocess with a timeout and retries.
+def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False,
+ logfile=None):
+ """Executes a subprocess with a timeout.
Args:
args: List of arguments to the program, the program to execute is the first
element.
- timeout: the timeout in seconds.
- retries: the number of retries.
+ timeout: the timeout in seconds or None to wait forever.
+ cwd: If not None, the subprocess's current directory will be changed to
+ |cwd| before it's executed.
+ shell: Whether to execute args as a shell command.
+ logfile: Optional file-like object that will receive output from the
+ command as it is running.
Returns:
The 2-tuple (exit code, output).
"""
- return timeout_retry.Run(GetCmdStatusAndOutput, timeout, retries, [args])
+ assert fcntl, 'fcntl module is required'
+ process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ try:
+ end_time = (time.time() + timeout) if timeout else None
+ poll_interval = 1
+ buffer_size = 4096
+ child_fd = process.stdout.fileno()
+ output = StringIO.StringIO()
+
+ # Enable non-blocking reads from the child's stdout.
+ fl = fcntl.fcntl(child_fd, fcntl.F_GETFL)
+ fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ while True:
+ if end_time and time.time() > end_time:
+ raise TimeoutError
+ read_fds, _, _ = select.select([child_fd], [], [], poll_interval)
+ if child_fd in read_fds:
+ data = os.read(child_fd, buffer_size)
+ if not data:
+ break
+ if logfile:
+ logfile.write(data)
+ output.write(data)
+ if process.poll() is not None:
+ break
+ finally:
+ try:
+ # Make sure the process doesn't stick around if we fail with an
+ # exception.
+ process.kill()
+ except OSError:
+ pass
+ process.wait()
+ return process.returncode, output.getvalue()
« no previous file with comments | « no previous file | build/android/pylib/perf/test_runner.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698