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

Side by Side Diff: client/utils/subprocess42.py

Issue 2037253002: run_isolated.py: install CIPD packages (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@master
Patch Set: Created 4 years, 6 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 unified diff | Download patch
OLDNEW
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
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
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
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)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698