Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """subprocess42 is the answer to life the universe and everything. | 5 """subprocess42 is the answer to life the universe and everything. |
| 6 | 6 |
| 7 It has the particularity of having a Popen implementation that can yield output | 7 It has the particularity of having a Popen implementation that can yield output |
| 8 as it is produced while implementing a timeout and NOT requiring the use of | 8 as it is produced while implementing a timeout and NOT requiring the use of |
| 9 worker threads. | 9 worker threads. |
| 10 | 10 |
| (...skipping 369 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 380 # communicate() uses wait() internally. | 380 # communicate() uses wait() internally. |
| 381 self.end = time.time() | 381 self.end = time.time() |
| 382 return self.returncode | 382 return self.returncode |
| 383 | 383 |
| 384 def poll(self): | 384 def poll(self): |
| 385 ret = super(Popen, self).poll() | 385 ret = super(Popen, self).poll() |
| 386 if ret is not None and not self.end: | 386 if ret is not None and not self.end: |
| 387 self.end = time.time() | 387 self.end = time.time() |
| 388 return ret | 388 return ret |
| 389 | 389 |
| 390 def yield_any_line(self, **kwargs): | |
| 391 """Yields lines until the process terminates. | |
| 392 | |
| 393 Like yield_any, but yields lines. | |
| 394 """ | |
| 395 return split(self.yield_any(**kwargs)) | |
| 396 | |
| 390 def yield_any(self, maxsize=None, timeout=None): | 397 def yield_any(self, maxsize=None, timeout=None): |
| 391 """Yields output until the process terminates. | 398 """Yields output until the process terminates. |
| 392 | 399 |
| 393 Unlike wait(), does not raise TimeoutExpired. | 400 Unlike wait(), does not raise TimeoutExpired. |
| 394 | 401 |
| 395 Yields: | 402 Yields: |
| 396 (pipename, data) where pipename is either 'stdout', 'stderr' or None in | 403 (pipename, data) where pipename is either 'stdout', 'stderr' or None in |
| 397 case of timeout or when the child process closed one of the pipe(s) and | 404 case of timeout or when the child process closed one of the pipe(s) and |
| 398 all pending data on the pipe was read. | 405 all pending data on the pipe was read. |
| 399 | 406 |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 411 old_timeout = timeout | 418 old_timeout = timeout |
| 412 timeout = lambda: old_timeout | 419 timeout = lambda: old_timeout |
| 413 else: | 420 else: |
| 414 assert callable(timeout), timeout | 421 assert callable(timeout), timeout |
| 415 | 422 |
| 416 if maxsize is not None and not callable(maxsize): | 423 if maxsize is not None and not callable(maxsize): |
| 417 assert isinstance(maxsize, (int, float)), maxsize | 424 assert isinstance(maxsize, (int, float)), maxsize |
| 418 | 425 |
| 419 last_yield = time.time() | 426 last_yield = time.time() |
| 420 while self.poll() is None: | 427 while self.poll() is None: |
| 421 to = (None if timeout is None | 428 to = timeout() if timeout else None |
|
M-A Ruel
2016/06/06 23:34:51
if you want to make this more readable, do:
to =
nodir
2016/06/07 18:46:35
to be clear: the purpose of the change was to allo
| |
| 422 else max(timeout() - (time.time() - last_yield), 0)) | 429 to = max(to - (time.time() - last_yield), 0) if to is not None else None |
| 423 t, data = self.recv_any( | 430 t, data = self.recv_any( |
| 424 maxsize=maxsize() if callable(maxsize) else maxsize, timeout=to) | 431 maxsize=maxsize() if callable(maxsize) else maxsize, timeout=to) |
| 425 if data or to is 0: | 432 if data or to is 0: |
| 426 yield t, data | 433 yield t, data |
| 427 last_yield = time.time() | 434 last_yield = time.time() |
| 428 | 435 |
| 429 # Read all remaining output in the pipes. | 436 # Read all remaining output in the pipes. |
| 430 # There is 3 cases: | 437 # There is 3 cases: |
| 431 # - pipes get closed automatically by the calling process before it exits | 438 # - pipes get closed automatically by the calling process before it exits |
| 432 # - pipes are closed automated by the OS | 439 # - pipes are closed automated by the OS |
| (...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 628 SEM_NOGPFAULTERRORBOX = 2 | 635 SEM_NOGPFAULTERRORBOX = 2 |
| 629 SEM_NOALIGNMENTFAULTEXCEPT = 0x8000 | 636 SEM_NOALIGNMENTFAULTEXCEPT = 0x8000 |
| 630 ctypes.windll.kernel32.SetErrorMode( | 637 ctypes.windll.kernel32.SetErrorMode( |
| 631 SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX| | 638 SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX| |
| 632 SEM_NOALIGNMENTFAULTEXCEPT) | 639 SEM_NOALIGNMENTFAULTEXCEPT) |
| 633 # TODO(maruel): Other OSes. | 640 # TODO(maruel): Other OSes. |
| 634 # - OSX, need to figure out a way to make the following process tree local: | 641 # - OSX, need to figure out a way to make the following process tree local: |
| 635 # defaults write com.apple.CrashReporter UseUNC 1 | 642 # defaults write com.apple.CrashReporter UseUNC 1 |
| 636 # defaults write com.apple.CrashReporter DialogType none | 643 # defaults write com.apple.CrashReporter DialogType none |
| 637 # - Ubuntu, disable apport if needed. | 644 # - Ubuntu, disable apport if needed. |
| 645 | |
| 646 | |
| 647 def split(data, sep='\n'): | |
| 648 """Splits pipe data by |sep|. Does some buffering. | |
| 649 | |
| 650 For example, [('stdout', 'a\nb'), ('stdout', '\n'), ('stderr', 'c\n')] -> | |
| 651 [('stdout', 'a'), ('stdout', 'b'), ('stderr', 'c')]. | |
| 652 | |
| 653 If a chunk of data from one pipe does not end with the separator, and next | |
|
Vadim Sh.
2016/06/06 21:32:14
I don't think that's a good idea (see below, also
nodir
2016/06/07 18:46:35
the only reason I did this is to buffer less, but
| |
| 654 chunk comes from another pipe, the end of the first chunk is returned as is. | |
| 655 E.g. [('stdout', 'a\nb'), ('stderr', 'c')] -> | |
| 656 [('stdout', 'a'), ('stdout', 'b'), ('stderr', 'c')]. | |
| 657 | |
| 658 Args: | |
| 659 data: iterable of tuples (pipe_name, bytes). | |
| 660 | |
| 661 Returns: | |
| 662 An iterator of tuples (pipe_name, bytes) where bytes is the input data | |
| 663 but split by sep into separate tuples. | |
| 664 """ | |
| 665 pending_chunks = None | |
| 666 pending_chunks_pipe_name = None | |
| 667 | |
| 668 for pipe_name, chunk in data: | |
| 669 start = 0 # offset in chunk to start |sep| search from | |
| 670 | |
| 671 if pending_chunks: | |
| 672 if pending_chunks_pipe_name != pipe_name: | |
|
Vadim Sh.
2016/06/06 21:32:14
why? interleaving of stdout and stderr in general
nodir
2016/06/07 18:46:35
removed, simplified
| |
| 673 # don't buffer more than one pipe at a time. | |
| 674 # yield pending chunk as is and forget. | |
| 675 yield pending_chunks_pipe_name, ''.join(pending_chunks) | |
| 676 pending_chunks = None | |
| 677 pending_chunks_pipe_name = None | |
| 678 else: | |
| 679 # it is the same pipe | |
| 680 | |
| 681 j = chunk.find(sep) | |
| 682 if j == -1: | |
| 683 # this chunk is incomplete either | |
| 684 pending_chunks.append(chunk) | |
| 685 continue | |
| 686 | |
| 687 # beginning of this chunk is a continuation of the pending one. | |
| 688 pending_chunks.append(chunk[:j]) | |
| 689 # now the pending chunk is complete: yield and forget | |
| 690 yield pending_chunks_pipe_name, ''.join(pending_chunks) | |
| 691 pending_chunks = None | |
| 692 pending_chunks_pipe_name = None | |
| 693 | |
| 694 start = j + 1 | |
| 695 | |
| 696 # split the chunk and yield parts that are followed by sep | |
| 697 while start < len(chunk): | |
| 698 j = chunk.find(sep, start) | |
| 699 if j != -1: | |
| 700 yield pipe_name, chunk[start:j] | |
| 701 start = j + 1 | |
| 702 else: | |
| 703 # last part is incomplete | |
| 704 pending_chunks = [chunk[start:]] | |
| 705 pending_chunks_pipe_name = pipe_name | |
| 706 break | |
| 707 # data is exhausted | |
| 708 | |
| 709 if pending_chunks: | |
| 710 yield pending_chunks_pipe_name, ''.join(pending_chunks) | |
| OLD | NEW |