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

Side by Side 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 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 cStringIO 11 import cStringIO
12 import errno 12 import errno
13 import logging 13 import logging
14 import os 14 import os
15 import Queue 15 import Queue
16 import select
16 import subprocess 17 import subprocess
17 import sys 18 import sys
18 import time 19 import time
19 import threading 20 import threading
20 21
22 if sys.platform != 'win32':
23 import fcntl
24
25
21 # Constants forwarded from subprocess. 26 # Constants forwarded from subprocess.
22 PIPE = subprocess.PIPE 27 PIPE = subprocess.PIPE
23 STDOUT = subprocess.STDOUT 28 STDOUT = subprocess.STDOUT
24 # Sends stdout or stderr to os.devnull. 29 # Sends stdout or stderr to os.devnull.
25 VOID = object() 30 VOID = object()
26 # Error code when a process was killed because it timed out. 31 # Error code when a process was killed because it timed out.
27 TIMED_OUT = -2001 32 TIMED_OUT = -2001
28 33
29 # Globals. 34 # Globals.
30 # Set to True if you somehow need to disable this hack. 35 # Set to True if you somehow need to disable this hack.
(...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after
196 None, 201 None,
197 'Visit ' 202 'Visit '
198 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to ' 203 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure to '
199 'learn how to fix this error; you need to rebase your cygwin dlls') 204 'learn how to fix this error; you need to rebase your cygwin dlls')
200 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go 205 # Popen() can throw OSError when cwd or args[0] doesn't exist. Let it go
201 # through 206 # through
202 raise 207 raise
203 208
204 209
205 def _queue_pipe_read(pipe, name, done, dest): 210 def _queue_pipe_read(pipe, name, done, dest):
206 """Queue characters read from a pipe into a queue. 211 """Queues characters read from a pipe into a queue.
207 212
208 Left outside the _tee_threads function to not introduce a function closure 213 Left outside the _tee_threads function to not introduce a function closure
209 to speed up variable lookup. 214 to speed up variable lookup.
210 """ 215 """
211 while not done.isSet(): 216 while not done.isSet():
212 data = pipe.read(1) 217 data = pipe.read(1)
213 if not data: 218 if not data:
214 break 219 break
215 dest.put((name, data)) 220 dest.put((name, data))
216 dest.put(name) 221 dest.put(name)
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
329 if proc.returncode is None: 334 if proc.returncode is None:
330 # Usually happens when killed with timeout but not listening to pipes. 335 # Usually happens when killed with timeout but not listening to pipes.
331 proc.wait() 336 proc.wait()
332 337
333 if timed_out: 338 if timed_out:
334 return TIMED_OUT 339 return TIMED_OUT
335 340
336 return proc.returncode 341 return proc.returncode
337 342
338 343
344 def _read_pipe(handles, pipe, out_fn):
345 """Reads bytes from a pipe and calls the output callback."""
346 data = pipe.read()
347 if not data:
348 del handles[pipe]
349 else:
350 out_fn(data)
351
352
353 def _tee_posix(proc, timeout, start, stdin, args, kwargs):
354 """Polls a process and its pipe using select.select().
355
356 TODO(maruel): Implement a non-polling method for OSes that support it.
357 """
358 handles_r = {}
359 if callable(kwargs.get('stdout')):
360 handles_r[proc.stdout] = lambda: _read_pipe(
361 handles_r, proc.stdout, kwargs['stdout'])
362 if callable(kwargs.get('stderr')):
363 handles_r[proc.stderr] = lambda: _read_pipe(
364 handles_r, proc.stderr, kwargs['stderr'])
365
366 handles_w = {}
367 if isinstance(stdin, str):
368 stdin_io = cStringIO.StringIO(stdin)
369 def write_stdin():
370 data = stdin_io.read(1)
371 if data:
372 proc.stdin.write(data)
373 else:
374 del handles_w[proc.stdin]
375 proc.stdin.close()
376 handles_w[proc.stdin] = write_stdin
377 else:
378 # TODO(maruel): Fix me, it could be VOID.
379 assert stdin is None
380
381 # Make all the file objects of the child process non-blocking file.
382 # TODO(maruel): Test if a pipe is handed to the child process.
383 for pipe in (proc.stdin, proc.stdout, proc.stderr):
384 fileno = pipe and getattr(pipe, 'fileno', lambda: None)()
385 if fileno:
386 # Note: making a pipe non-blocking means the C stdio could act wrong. In
387 # particular, readline() cannot be used. Work around is to use os.read().
388 fl = fcntl.fcntl(fileno, fcntl.F_GETFL)
389 fcntl.fcntl(fileno, fcntl.F_SETFL, fl | os.O_NONBLOCK)
390
391 timed_out = False
392 while handles_r or handles_w or (timeout and proc.poll() is None):
393 period = None
394 if timeout:
395 period = max(0, timeout - (time.time() - start))
396 if not period and not timed_out:
397 proc.kill()
398 timed_out = True
399 if timed_out:
400 period = 0.001
401
402 # It reconstructs objects on each loop, not very efficient.
403 reads, writes, _, = select.select(
404 handles_r.keys(), handles_w.keys(), [], period)
405 for read in reads:
406 handles_r[read]()
407 for write in writes:
408 handles_w[write]()
409
410 # No pipe open anymore and if there was a time out, the child process was
411 # killed already.
412 proc.wait()
413 if timed_out:
414 return TIMED_OUT
415 return proc.returncode
416
417
339 def communicate(args, timeout=None, **kwargs): 418 def communicate(args, timeout=None, **kwargs):
340 """Wraps subprocess.Popen().communicate(). 419 """Wraps subprocess.Popen().communicate().
341 420
342 Returns ((stdout, stderr), returncode). 421 Returns ((stdout, stderr), returncode).
343 422
344 - The process will be killed after |timeout| seconds and returncode set to 423 - The process will be killed after |timeout| seconds and returncode set to
345 TIMED_OUT. 424 TIMED_OUT.
346 - Automatically passes stdin content as input so do not specify stdin=PIPE. 425 - Automatically passes stdin content as input so do not specify stdin=PIPE.
347 """ 426 """
348 if timeout and kwargs.get('shell'): 427 if timeout and kwargs.get('shell'):
(...skipping 30 matching lines...) Expand all
379 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait 458 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
380 # When the pipe fills up, it will deadlock this process. Using a thread 459 # When the pipe fills up, it will deadlock this process. Using a thread
381 # works around that issue. No need for thread safe function since the call 460 # works around that issue. No need for thread safe function since the call
382 # backs are guaranteed to be called from the main thread. 461 # backs are guaranteed to be called from the main thread.
383 if kwargs.get('stdout') == PIPE: 462 if kwargs.get('stdout') == PIPE:
384 stdout = [] 463 stdout = []
385 kwargs['stdout'] = stdout.append 464 kwargs['stdout'] = stdout.append
386 if kwargs.get('stderr') == PIPE: 465 if kwargs.get('stderr') == PIPE:
387 stderr = [] 466 stderr = []
388 kwargs['stderr'] = stderr.append 467 kwargs['stderr'] = stderr.append
389 returncode = _tee_threads(proc, timeout, start, stdin, args, kwargs) 468 if sys.platform == 'win32':
469 # On cygwin, ctypes._FUNCFLAG_STDCALL, which is used by ctypes.WINFUNCTYPE,
470 # doesn't exist so _tee_win() cannot be used yet.
471 returncode = _tee_threads(proc, timeout, start, stdin, args, kwargs)
472 else:
473 returncode = _tee_posix(proc, timeout, start, stdin, args, kwargs)
390 if not stdout is None: 474 if not stdout is None:
391 stdout = ''.join(stdout) 475 stdout = ''.join(stdout)
392 if not stderr is None: 476 if not stderr is None:
393 stderr = ''.join(stderr) 477 stderr = ''.join(stderr)
394 return (stdout, stderr), returncode 478 return (stdout, stderr), returncode
395 479
396 480
397 def call(args, **kwargs): 481 def call(args, **kwargs):
398 """Emulates subprocess.call(). 482 """Emulates subprocess.call().
399 483
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
445 529
446 Captures stdout of a process call and returns stdout only. 530 Captures stdout of a process call and returns stdout only.
447 531
448 - Throws if return code is not 0. 532 - Throws if return code is not 0.
449 - Works even prior to python 2.7. 533 - Works even prior to python 2.7.
450 - Blocks stdin by default if not specified since no output will be visible. 534 - Blocks stdin by default if not specified since no output will be visible.
451 - As per doc, "The stdout argument is not allowed as it is used internally." 535 - As per doc, "The stdout argument is not allowed as it is used internally."
452 """ 536 """
453 kwargs.setdefault('stdin', VOID) 537 kwargs.setdefault('stdin', VOID)
454 return check_call_out(args, stdout=PIPE, **kwargs)[0] 538 return check_call_out(args, stdout=PIPE, **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