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

Unified Diff: gclient_utils.py

Issue 2300433003: gclient_utils: provide subproces42 based CheckCallAndFilter. (Closed)
Patch Set: Created 4 years, 4 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 | tests/gclient_utils_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: gclient_utils.py
diff --git a/gclient_utils.py b/gclient_utils.py
index 556018105cc8cd0c30e42f0ec6e235efbe900eeb..93c0a7e46cba08364401c9d6cc995fc9f212f153 100644
--- a/gclient_utils.py
+++ b/gclient_utils.py
@@ -21,7 +21,9 @@ import threading
import time
import urlparse
+# TODO(tandrii): migrate users to subprocess42.
import subprocess2
+import subprocess42
RETRY_MAX = 3
@@ -269,8 +271,10 @@ def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
"""Adds 'header' support to CheckCallAndFilter.
If |always| is True, a message indicating what is being done
- is printed to stdout all the time even if not output is generated. Otherwise
+ is printed to stdout all the time even if no output is generated. Otherwise
the message header is printed only if the call generated any ouput.
+
+ If use_v42=True is given, uses subprocess42 through CheckCallAndFilter42.
"""
stdout = kwargs.setdefault('stdout', sys.stdout)
if header is None:
@@ -290,6 +294,8 @@ def CheckCallAndFilterAndHeader(args, always=False, header=None, **kwargs):
kwargs['call_filter_on_first_line'] = True
# Obviously.
kwargs.setdefault('print_stdout', True)
+ if kwargs.pop('use_v42', False):
+ return CheckCallAndFilter42(args, **kwargs)
return CheckCallAndFilter(args, **kwargs)
@@ -521,6 +527,7 @@ class _KillTimer(object):
return
+#TODO(tandrii): all users of this should be converted to CheckCallAndFilter42.
def CheckCallAndFilter(args, stdout=None, filter_fn=None,
print_stdout=None, call_filter_on_first_line=False,
retry=False, kill_timeout=None, **kwargs):
@@ -613,6 +620,104 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
rv, args, kwargs.get('cwd', None), None, None)
+def CheckCallAndFilter42(args, stdout=None, filter_fn=None,
+ print_stdout=None, call_filter_on_first_line=False,
+ retry=False, kill_timeout=None, **kwargs):
+ """Runs a command and calls back a filter function if needed.
+
+ Accepts all subprocess2.Popen() parameters plus:
+ print_stdout: If True, the command's stdout is forwarded to stdout.
+ filter_fn: A function taking a single string argument called with each line
+ of the subprocess2's output. Each line has the trailing newline
+ character trimmed.
+ stdout: Can be any bufferable output.
+ retry: If the process exits non-zero, sleep for a brief interval and try
+ again, up to RETRY_MAX times.
+ kill_timeout: (float) if given, number of seconds after which spawned
+ process would be killed. This applies to each retry separately.
+
+ stderr is always redirected to stdout.
+ """
+ assert print_stdout or filter_fn
+ if kill_timeout:
+ assert isinstance(kill_timeout, (int, float)), kill_timeout
+
+ stdout = stdout or sys.stdout
+ output = cStringIO.StringIO()
+ filter_fn = filter_fn or (lambda x: None)
+
+ sleep_interval = RETRY_INITIAL_SLEEP
+ run_cwd = kwargs.get('cwd', os.getcwd())
+ debug_child_info = "'%s' in %s" % (' '.join('"%s"' % x for x in args), run_cwd)
+
+ for _ in xrange(RETRY_MAX + 1):
+ child = subprocess42.Popen(
+ args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
+ **kwargs)
+
+ GClientChildren.add(child)
+
+ # Do a flush of stdout before we begin reading from the subprocess42's
+ # stdout.
+ stdout.flush()
+
+ # Also, we need to forward stdout to prevent weird re-ordering of output.
+ try:
+ first_line = True
+ in_line = ''
+ # Don't block for more than 1 second.
+ for pipe, data in child.yield_any(maxsize=256, timeout=1):
+ if pipe is None:
+ # No new data.
+ if kill_timeout and child.duration() > kill_timeout:
+ print('ERROR: killing process %s running for %.0fs (timeout: %.0fs)'
+ % (debug_child_info, child.duration(), kill_timeout))
+ child.kill()
+ continue
+
+ assert pipe == 'stdout'
+ if first_line:
+ first_line = False
+ if call_filter_on_first_line:
+ filter_fn(None)
+
+ output.write(data)
+ if print_stdout:
+ stdout.write(data)
+ for byte in data:
+ if byte not in ['\r', '\n']:
+ in_line += byte
+ else:
+ filter_fn(in_line)
+ in_line = ''
+ # Flush the rest of buffered output. This is only an issue with
+ # stdout/stderr not ending with a \n.
+ if len(in_line):
+ filter_fn(in_line)
+ rv = child.wait()
+
+ # Don't put this in a 'finally,' since the child may still run if we get
+ # an exception.
+ GClientChildren.remove(child)
+
+ except KeyboardInterrupt:
+ print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
+ raise
+
+ if rv == 0:
+ return output.getvalue()
+ if not retry:
+ break
+ print ("WARNING: subprocess %s failed; will retry after a short nap..." %
+ debug_child_info)
+ time.sleep(sleep_interval)
+ sleep_interval *= 2
+ # TODO(tandrii): change this to subprocess.CalledProcessError,
+ # beacuse subprocess42 doesn't have a class unlike subprocess2.
+ raise subprocess2.CalledProcessError(
+ rv, args, kwargs.get('cwd', None), None, None)
+
+
class GitFilter(object):
"""A filter_fn implementation for quieting down git output messages.
« no previous file with comments | « no previous file | tests/gclient_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698