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

Unified Diff: subprocess2.py

Issue 8462008: Implement accelerated tee support for POSIX. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Add comments Created 9 years, 1 month 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/subprocess2_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: subprocess2.py
diff --git a/subprocess2.py b/subprocess2.py
index 810631ae225feae33cfa53b62bd5ee44e1c1f6e6..430010c144afd6e3798fd1e8bd7b22cf94374d6b 100644
--- a/subprocess2.py
+++ b/subprocess2.py
@@ -13,11 +13,16 @@ import errno
import logging
import os
import Queue
+import select
import subprocess
import sys
import time
import threading
+if sys.platform != 'win32':
+ import fcntl
+
+
# Constants forwarded from subprocess.
PIPE = subprocess.PIPE
STDOUT = subprocess.STDOUT
@@ -203,7 +208,7 @@ def Popen(args, **kwargs):
def _queue_pipe_read(pipe, name, done, dest):
- """Queue characters read from a pipe into a queue.
+ """Queues characters read from a pipe into a queue.
Left outside the _tee_threads function to not introduce a function closure
to speed up variable lookup.
@@ -336,6 +341,80 @@ def _tee_threads(proc, timeout, start, stdin, args, kwargs):
return proc.returncode
+def _read_pipe(handles, pipe, out_fn):
+ """Reads bytes from a pipe and calls the output callback."""
+ data = pipe.read()
+ if not data:
+ del handles[pipe]
+ else:
+ out_fn(data)
+
+
+def _tee_posix(proc, timeout, start, stdin, args, kwargs):
+ """Polls a process and its pipe using select.select().
+
+ TODO(maruel): Implement a non-polling method for OSes that support it.
+ """
+ handles_r = {}
+ if callable(kwargs.get('stdout')):
+ handles_r[proc.stdout] = lambda: _read_pipe(
+ handles_r, proc.stdout, kwargs['stdout'])
+ if callable(kwargs.get('stderr')):
+ handles_r[proc.stderr] = lambda: _read_pipe(
+ handles_r, proc.stderr, kwargs['stderr'])
+
+ handles_w = {}
+ if isinstance(stdin, str):
+ stdin_io = cStringIO.StringIO(stdin)
+ def write_stdin():
+ data = stdin_io.read(1)
+ if data:
+ proc.stdin.write(data)
+ else:
+ del handles_w[proc.stdin]
+ proc.stdin.close()
+ handles_w[proc.stdin] = write_stdin
+ else:
+ # TODO(maruel): Fix me, it could be VOID.
+ assert stdin is None
+
+ # Make all the file objects of the child process non-blocking file.
+ # TODO(maruel): Test if a pipe is handed to the child process.
+ for pipe in (proc.stdin, proc.stdout, proc.stderr):
+ fileno = pipe and getattr(pipe, 'fileno', lambda: None)()
+ if fileno:
+ # Note: making a pipe non-blocking means the C stdio could act wrong. In
+ # particular, readline() cannot be used. Work around is to use os.read().
+ fl = fcntl.fcntl(fileno, fcntl.F_GETFL)
+ fcntl.fcntl(fileno, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ timed_out = False
+ while handles_r or handles_w or (timeout and proc.poll() is None):
+ period = None
+ if timeout:
+ period = max(0, timeout - (time.time() - start))
+ if not period and not timed_out:
+ proc.kill()
+ timed_out = True
+ if timed_out:
+ period = 0.001
+
+ # It reconstructs objects on each loop, not very efficient.
+ reads, writes, _, = select.select(
+ handles_r.keys(), handles_w.keys(), [], period)
+ for read in reads:
+ handles_r[read]()
+ for write in writes:
+ handles_w[write]()
+
+ # No pipe open anymore and if there was a time out, the child process was
+ # killed already.
+ proc.wait()
+ if timed_out:
+ return TIMED_OUT
+ return proc.returncode
+
+
def communicate(args, timeout=None, **kwargs):
"""Wraps subprocess.Popen().communicate().
@@ -386,7 +465,12 @@ def communicate(args, timeout=None, **kwargs):
if kwargs.get('stderr') == PIPE:
stderr = []
kwargs['stderr'] = stderr.append
- returncode = _tee_threads(proc, timeout, start, stdin, args, kwargs)
+ if sys.platform == 'win32':
+ # On cygwin, ctypes._FUNCFLAG_STDCALL, which is used by ctypes.WINFUNCTYPE,
+ # doesn't exist so _tee_win() cannot be used yet.
+ returncode = _tee_threads(proc, timeout, start, stdin, args, kwargs)
+ else:
+ returncode = _tee_posix(proc, timeout, start, stdin, args, kwargs)
if not stdout is None:
stdout = ''.join(stdout)
if not stderr is None:
« 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