Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Generic utils.""" | 5 """Generic utils.""" |
| 6 | 6 |
| 7 import codecs | 7 import codecs |
| 8 import cStringIO | 8 import cStringIO |
| 9 import datetime | 9 import datetime |
| 10 import logging | 10 import logging |
| (...skipping 448 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 459 print >> sys.stderr, ' ', zombie.pid | 459 print >> sys.stderr, ' ', zombie.pid |
| 460 | 460 |
| 461 | 461 |
| 462 class _KillTimer(object): | 462 class _KillTimer(object): |
| 463 """Timer that kills child process after certain interval since last poke or | 463 """Timer that kills child process after certain interval since last poke or |
| 464 creation. | 464 creation. |
| 465 """ | 465 """ |
| 466 # TODO(tandrii): we really want to make use of subprocess42 here, and not | 466 # TODO(tandrii): we really want to make use of subprocess42 here, and not |
| 467 # re-invent the wheel, but it's too much work :( | 467 # re-invent the wheel, but it's too much work :( |
| 468 | 468 |
| 469 def __init__(self, timeout, child): | 469 def __init__(self, timeout, child, child_info): |
| 470 self._timeout = timeout | 470 self._timeout = timeout |
| 471 self._child = child | 471 self._child = child |
| 472 self._child_info = child_info | |
| 472 | 473 |
| 473 self._cv = threading.Condition() | 474 self._cv = threading.Condition() |
| 474 # All items below are protected by condition above. | 475 # All items below are protected by condition above. |
| 475 self._kill_at = None | 476 self._kill_at = None |
| 477 self._killing_attempted = False | |
| 476 self._working = True | 478 self._working = True |
| 477 self._thread = None | 479 self._thread = None |
| 478 | 480 |
| 479 # Start the timer immediately. | 481 # Start the timer immediately. |
| 480 if self._timeout: | 482 if self._timeout: |
| 481 self._kill_at = time.time() + self._timeout | 483 self._kill_at = time.time() + self._timeout |
| 482 self._thread = threading.Thread(name='_KillTimer', target=self._work) | 484 self._thread = threading.Thread(name='_KillTimer', target=self._work) |
| 483 self._thread.daemon = True | 485 self._thread.daemon = True |
| 484 self._thread.start() | 486 self._thread.start() |
| 485 | 487 |
| 488 @property | |
| 489 def killing_attempted(self): | |
| 490 return self._killing_attempted | |
| 491 | |
| 486 def poke(self): | 492 def poke(self): |
| 487 if not self._timeout: | 493 if not self._timeout: |
| 488 return | 494 return |
| 489 with self._cv: | 495 with self._cv: |
| 490 self._kill_at = time.time() + self._timeout | 496 self._kill_at = time.time() + self._timeout |
| 491 | 497 |
| 492 def cancel(self): | 498 def cancel(self): |
| 493 with self._cv: | 499 with self._cv: |
| 494 self._working = False | 500 self._working = False |
| 495 self._cv.notifyAll() | 501 self._cv.notifyAll() |
| 496 | 502 |
| 497 def _work(self): | 503 def _work(self): |
| 498 if not self._timeout: | 504 if not self._timeout: |
| 499 return | 505 return |
| 500 while True: | 506 while True: |
| 501 with self._cv: | 507 with self._cv: |
| 502 if not self._working: | 508 if not self._working: |
| 503 return | 509 return |
| 504 left = self._kill_at - time.time() | 510 left = self._kill_at - time.time() |
| 505 if left > 0: | 511 if left > 0: |
| 506 self._cv.wait(timeout=left) | 512 self._cv.wait(timeout=left) |
| 507 continue | 513 continue |
| 508 try: | 514 try: |
| 509 logging.warn('killing child %s because of no output for %fs', | 515 logging.warn('killing child %s %s because of no output for %fs', |
| 510 self._child, self._timeout) | 516 self._child.pid, self._child_info, self._timeout) |
| 517 self._killing_attempted = True | |
| 511 self._child.kill() | 518 self._child.kill() |
| 512 except OSError: | 519 except OSError: |
| 513 logging.exception('failed to kill child %s', self._child) | 520 logging.exception('failed to kill child %s', self._child) |
| 514 return | 521 return |
| 515 | 522 |
| 516 | 523 |
| 517 def CheckCallAndFilter(args, stdout=None, filter_fn=None, | 524 def CheckCallAndFilter(args, stdout=None, filter_fn=None, |
| 518 print_stdout=None, call_filter_on_first_line=False, | 525 print_stdout=None, call_filter_on_first_line=False, |
| 519 retry=False, kill_timeout=None, **kwargs): | 526 retry=False, kill_timeout=None, **kwargs): |
| 520 """Runs a command and calls back a filter function if needed. | 527 """Runs a command and calls back a filter function if needed. |
| 521 | 528 |
| 522 Accepts all subprocess2.Popen() parameters plus: | 529 Accepts all subprocess2.Popen() parameters plus: |
| 523 print_stdout: If True, the command's stdout is forwarded to stdout. | 530 print_stdout: If True, the command's stdout is forwarded to stdout. |
| 524 filter_fn: A function taking a single string argument called with each line | 531 filter_fn: A function taking a single string argument called with each line |
| 525 of the subprocess2's output. Each line has the trailing newline | 532 of the subprocess2's output. Each line has the trailing newline |
| 526 character trimmed. | 533 character trimmed. |
| 527 stdout: Can be any bufferable output. | 534 stdout: Can be any bufferable output. |
| 528 retry: If the process exits non-zero, sleep for a brief interval and try | 535 retry: If the process exits non-zero, sleep for a brief interval and try |
| 529 again, up to RETRY_MAX times. | 536 again, up to RETRY_MAX times. |
| 530 kill_timeout: (float) if given, number of seconds after which process would | 537 kill_timeout: (float) if given, number of seconds after which process would |
| 531 be killed if there is no output. Must not be used with shell=True as | 538 be killed. Must not be used with shell=True as only shell process |
| 532 only shell process would be killed, but not processes spawned by | 539 would be killed, but not processes spawned by shell. |
| 533 shell. | |
| 534 | 540 |
| 535 stderr is always redirected to stdout. | 541 stderr is always redirected to stdout. |
| 536 """ | 542 """ |
| 537 assert print_stdout or filter_fn | 543 assert print_stdout or filter_fn |
| 538 assert not kwargs.get('shell', False) or not kill_timeout, ( | 544 assert not kwargs.get('shell', False) or not kill_timeout, ( |
| 539 'kill_timeout should not be used with shell=True') | 545 'kill_timeout should not be used with shell=True') |
| 540 stdout = stdout or sys.stdout | 546 stdout = stdout or sys.stdout |
| 541 output = cStringIO.StringIO() | 547 output = cStringIO.StringIO() |
| 542 filter_fn = filter_fn or (lambda x: None) | 548 filter_fn = filter_fn or (lambda x: None) |
| 543 | 549 |
| 544 sleep_interval = RETRY_INITIAL_SLEEP | 550 sleep_interval = RETRY_INITIAL_SLEEP |
| 545 run_cwd = kwargs.get('cwd', os.getcwd()) | 551 run_cwd = kwargs.get('cwd', os.getcwd()) |
| 552 debug_kid_info = "'%s' in %s" % (' '.join('"%s"' % x for x in args), run_cwd) | |
|
Michael Achenbach
2016/08/30 15:02:52
Wouldn't str(args) be sufficient?
Never mind. Saw
| |
| 553 | |
| 546 for _ in xrange(RETRY_MAX + 1): | 554 for _ in xrange(RETRY_MAX + 1): |
| 547 kid = subprocess2.Popen( | 555 kid = subprocess2.Popen( |
| 548 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, | 556 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, |
| 549 **kwargs) | 557 **kwargs) |
| 550 | 558 |
| 551 GClientChildren.add(kid) | 559 GClientChildren.add(kid) |
| 552 | 560 |
| 553 # Do a flush of stdout before we begin reading from the subprocess2's stdout | 561 # Do a flush of stdout before we begin reading from the subprocess2's stdout |
| 554 stdout.flush() | 562 stdout.flush() |
| 555 | 563 |
| 556 # Also, we need to forward stdout to prevent weird re-ordering of output. | 564 # Also, we need to forward stdout to prevent weird re-ordering of output. |
| 557 # This has to be done on a per byte basis to make sure it is not buffered: | 565 # This has to be done on a per byte basis to make sure it is not buffered: |
| 558 # normally buffering is done for each line, but if svn requests input, no | 566 # normally buffering is done for each line, but if svn requests input, no |
| 559 # end-of-line character is output after the prompt and it would not show up. | 567 # end-of-line character is output after the prompt and it would not show up. |
| 560 try: | 568 try: |
| 561 timeout_killer = _KillTimer(kill_timeout, kid) | 569 timeout_killer = _KillTimer(kill_timeout, kid, debug_kid_info) |
|
Michael Achenbach
2016/08/30 15:02:52
naming nit: child vs. kid - well, it's from the ol
| |
| 562 in_byte = kid.stdout.read(1) | 570 in_byte = kid.stdout.read(1) |
| 563 if in_byte: | 571 if in_byte: |
| 564 if call_filter_on_first_line: | 572 if call_filter_on_first_line: |
| 565 filter_fn(None) | 573 filter_fn(None) |
| 566 in_line = '' | 574 in_line = '' |
| 567 while in_byte: | 575 while in_byte: |
| 568 timeout_killer.poke() | |
| 569 output.write(in_byte) | 576 output.write(in_byte) |
| 570 if print_stdout: | 577 if print_stdout: |
| 571 stdout.write(in_byte) | 578 stdout.write(in_byte) |
| 572 if in_byte not in ['\r', '\n']: | 579 if in_byte not in ['\r', '\n']: |
| 573 in_line += in_byte | 580 in_line += in_byte |
| 574 else: | 581 else: |
| 575 filter_fn(in_line) | 582 filter_fn(in_line) |
| 576 in_line = '' | 583 in_line = '' |
| 577 in_byte = kid.stdout.read(1) | 584 in_byte = kid.stdout.read(1) |
| 578 # Flush the rest of buffered output. This is only an issue with | 585 # Flush the rest of buffered output. This is only an issue with |
| 579 # stdout/stderr not ending with a \n. | 586 # stdout/stderr not ending with a \n. |
| 580 if len(in_line): | 587 if len(in_line): |
| 581 filter_fn(in_line) | 588 filter_fn(in_line) |
| 582 rv = kid.wait() | 589 rv = kid.wait() |
| 583 timeout_killer.cancel() | 590 timeout_killer.cancel() |
| 584 | 591 |
| 585 # Don't put this in a 'finally,' since the child may still run if we get | 592 # Don't put this in a 'finally,' since the child may still run if we get |
| 586 # an exception. | 593 # an exception. |
| 587 GClientChildren.remove(kid) | 594 GClientChildren.remove(kid) |
| 588 | 595 |
| 589 except KeyboardInterrupt: | 596 except KeyboardInterrupt: |
| 590 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) | 597 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) |
| 591 raise | 598 raise |
| 592 | 599 |
| 593 if rv == 0: | 600 if rv == 0: |
| 594 return output.getvalue() | 601 return output.getvalue() |
| 595 if not retry: | 602 if not retry: |
| 596 break | 603 break |
| 597 print ("WARNING: subprocess '%s' in %s failed; will retry after a short " | 604 print ("WARNING: subprocess %s failed; will retry after a short nap..." % |
| 598 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd)) | 605 debug_kid_info) |
| 606 if timeout_killer.killing_attempted: | |
| 607 print('The subprocess above was likely killed because it looked hung. ' | |
| 608 'Output thus far:\n> %s' % | |
| 609 ('\n> '.join(output.getvalue().splitlines()))) | |
| 599 time.sleep(sleep_interval) | 610 time.sleep(sleep_interval) |
| 600 sleep_interval *= 2 | 611 sleep_interval *= 2 |
| 601 raise subprocess2.CalledProcessError( | 612 raise subprocess2.CalledProcessError( |
| 602 rv, args, kwargs.get('cwd', None), None, None) | 613 rv, args, kwargs.get('cwd', None), None, None) |
| 603 | 614 |
| 604 | 615 |
| 605 class GitFilter(object): | 616 class GitFilter(object): |
| 606 """A filter_fn implementation for quieting down git output messages. | 617 """A filter_fn implementation for quieting down git output messages. |
| 607 | 618 |
| 608 Allows a custom function to skip certain lines (predicate), and will throttle | 619 Allows a custom function to skip certain lines (predicate), and will throttle |
| (...skipping 674 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1283 # Just incase we have some ~/blah paths. | 1294 # Just incase we have some ~/blah paths. |
| 1284 target = os.path.abspath(os.path.expanduser(target)) | 1295 target = os.path.abspath(os.path.expanduser(target)) |
| 1285 if os.path.isfile(target) and os.access(target, os.X_OK): | 1296 if os.path.isfile(target) and os.access(target, os.X_OK): |
| 1286 return target | 1297 return target |
| 1287 if sys.platform.startswith('win'): | 1298 if sys.platform.startswith('win'): |
| 1288 for suffix in ('.bat', '.cmd', '.exe'): | 1299 for suffix in ('.bat', '.cmd', '.exe'): |
| 1289 alt_target = target + suffix | 1300 alt_target = target + suffix |
| 1290 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK): | 1301 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK): |
| 1291 return alt_target | 1302 return alt_target |
| 1292 return None | 1303 return None |
| OLD | NEW |