OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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] |
OLD | NEW |