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 441 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
452 time.sleep(0.5) | 452 time.sleep(0.5) |
453 GClientChildren._attemptToKillChildren() | 453 GClientChildren._attemptToKillChildren() |
454 | 454 |
455 with GCLIENT_CHILDREN_LOCK: | 455 with GCLIENT_CHILDREN_LOCK: |
456 if GCLIENT_CHILDREN: | 456 if GCLIENT_CHILDREN: |
457 print >> sys.stderr, 'Could not kill the following subprocesses:' | 457 print >> sys.stderr, 'Could not kill the following subprocesses:' |
458 for zombie in GCLIENT_CHILDREN: | 458 for zombie in GCLIENT_CHILDREN: |
459 print >> sys.stderr, ' ', zombie.pid | 459 print >> sys.stderr, ' ', zombie.pid |
460 | 460 |
461 | 461 |
462 class _KillTimer(object): | |
463 """Timer that kills child process after certain interval since last poke or | |
464 creation. | |
465 """ | |
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 :( | |
468 | |
469 def __init__(self, timeout, child): | |
470 self._timeout = timeout | |
471 self._child = child | |
472 | |
473 self._cv = threading.Condition() | |
474 # All items below are protected by condition above. | |
475 self._kill_at = None | |
476 self._working = True | |
477 self._thread = None | |
478 | |
479 # Start the timer immediately. | |
480 if self._timeout: | |
481 self._kill_at = time.time() + self._timeout | |
482 self._thread = threading.Thread(name='_KillTimer', target=self._work) | |
483 self._thread.daemon = True | |
484 self._thread.start() | |
485 | |
486 def poke(self): | |
487 if not self._timeout: | |
488 return | |
489 with self._cv: | |
490 self._kill_at = time.time() + self._timeout | |
491 | |
492 def cancel(self): | |
493 with self._cv: | |
494 self._working = False | |
495 self._cv.notifyAll() | |
496 | |
497 def _work(self): | |
498 if not self._timeout: | |
499 return | |
500 while True: | |
501 with self._cv: | |
502 if not self._working: | |
503 return | |
504 left = self._kill_at - time.time() | |
505 if left > 0: | |
506 self._cv.wait(timeout=left) | |
507 continue | |
508 try: | |
509 logging.warn('killing child %s because of no output for %fs', | |
510 self._child, self._timeout) | |
511 self._child.kill() | |
512 except OSError: | |
513 logging.exception('failed to kill child %s', self._child) | |
514 return | |
515 | |
516 | |
517 def CheckCallAndFilter(args, stdout=None, filter_fn=None, | 462 def CheckCallAndFilter(args, stdout=None, filter_fn=None, |
518 print_stdout=None, call_filter_on_first_line=False, | 463 print_stdout=None, call_filter_on_first_line=False, |
519 retry=False, kill_timeout=None, **kwargs): | 464 retry=False, **kwargs): |
520 """Runs a command and calls back a filter function if needed. | 465 """Runs a command and calls back a filter function if needed. |
521 | 466 |
522 Accepts all subprocess2.Popen() parameters plus: | 467 Accepts all subprocess2.Popen() parameters plus: |
523 print_stdout: If True, the command's stdout is forwarded to stdout. | 468 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 | 469 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 | 470 of the subprocess2's output. Each line has the trailing newline |
526 character trimmed. | 471 character trimmed. |
527 stdout: Can be any bufferable output. | 472 stdout: Can be any bufferable output. |
528 retry: If the process exits non-zero, sleep for a brief interval and try | 473 retry: If the process exits non-zero, sleep for a brief interval and try |
529 again, up to RETRY_MAX times. | 474 again, up to RETRY_MAX times. |
530 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 | |
532 only shell process would be killed, but not processes spawned by | |
533 shell. | |
534 | 475 |
535 stderr is always redirected to stdout. | 476 stderr is always redirected to stdout. |
536 """ | 477 """ |
537 assert print_stdout or filter_fn | 478 assert print_stdout or filter_fn |
538 assert not kwargs.get('shell', False) or not kill_timeout, ( | |
539 'kill_timeout should not be used with shell=True') | |
540 stdout = stdout or sys.stdout | 479 stdout = stdout or sys.stdout |
541 output = cStringIO.StringIO() | 480 output = cStringIO.StringIO() |
542 filter_fn = filter_fn or (lambda x: None) | 481 filter_fn = filter_fn or (lambda x: None) |
543 | 482 |
544 sleep_interval = RETRY_INITIAL_SLEEP | 483 sleep_interval = RETRY_INITIAL_SLEEP |
545 run_cwd = kwargs.get('cwd', os.getcwd()) | 484 run_cwd = kwargs.get('cwd', os.getcwd()) |
546 for _ in xrange(RETRY_MAX + 1): | 485 for _ in xrange(RETRY_MAX + 1): |
547 kid = subprocess2.Popen( | 486 kid = subprocess2.Popen( |
548 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, | 487 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, |
549 **kwargs) | 488 **kwargs) |
550 | 489 |
551 GClientChildren.add(kid) | 490 GClientChildren.add(kid) |
552 | 491 |
553 # Do a flush of stdout before we begin reading from the subprocess2's stdout | 492 # Do a flush of stdout before we begin reading from the subprocess2's stdout |
554 stdout.flush() | 493 stdout.flush() |
555 | 494 |
556 # Also, we need to forward stdout to prevent weird re-ordering of output. | 495 # 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: | 496 # 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 | 497 # 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. | 498 # end-of-line character is output after the prompt and it would not show up. |
560 try: | 499 try: |
561 timeout_killer = _KillTimer(kill_timeout, kid) | |
562 in_byte = kid.stdout.read(1) | 500 in_byte = kid.stdout.read(1) |
563 if in_byte: | 501 if in_byte: |
564 if call_filter_on_first_line: | 502 if call_filter_on_first_line: |
565 filter_fn(None) | 503 filter_fn(None) |
566 in_line = '' | 504 in_line = '' |
567 while in_byte: | 505 while in_byte: |
568 timeout_killer.poke() | |
569 output.write(in_byte) | 506 output.write(in_byte) |
570 if print_stdout: | 507 if print_stdout: |
571 stdout.write(in_byte) | 508 stdout.write(in_byte) |
572 if in_byte not in ['\r', '\n']: | 509 if in_byte not in ['\r', '\n']: |
573 in_line += in_byte | 510 in_line += in_byte |
574 else: | 511 else: |
575 filter_fn(in_line) | 512 filter_fn(in_line) |
576 in_line = '' | 513 in_line = '' |
577 in_byte = kid.stdout.read(1) | 514 in_byte = kid.stdout.read(1) |
578 # Flush the rest of buffered output. This is only an issue with | 515 # Flush the rest of buffered output. This is only an issue with |
579 # stdout/stderr not ending with a \n. | 516 # stdout/stderr not ending with a \n. |
580 if len(in_line): | 517 if len(in_line): |
581 filter_fn(in_line) | 518 filter_fn(in_line) |
582 rv = kid.wait() | 519 rv = kid.wait() |
583 timeout_killer.cancel() | |
584 | 520 |
585 # Don't put this in a 'finally,' since the child may still run if we get | 521 # Don't put this in a 'finally,' since the child may still run if we get |
586 # an exception. | 522 # an exception. |
587 GClientChildren.remove(kid) | 523 GClientChildren.remove(kid) |
588 | 524 |
589 except KeyboardInterrupt: | 525 except KeyboardInterrupt: |
590 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) | 526 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) |
591 raise | 527 raise |
592 | 528 |
593 if rv == 0: | 529 if rv == 0: |
(...skipping 695 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1289 # Just incase we have some ~/blah paths. | 1225 # Just incase we have some ~/blah paths. |
1290 target = os.path.abspath(os.path.expanduser(target)) | 1226 target = os.path.abspath(os.path.expanduser(target)) |
1291 if os.path.isfile(target) and os.access(target, os.X_OK): | 1227 if os.path.isfile(target) and os.access(target, os.X_OK): |
1292 return target | 1228 return target |
1293 if sys.platform.startswith('win'): | 1229 if sys.platform.startswith('win'): |
1294 for suffix in ('.bat', '.cmd', '.exe'): | 1230 for suffix in ('.bat', '.cmd', '.exe'): |
1295 alt_target = target + suffix | 1231 alt_target = target + suffix |
1296 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK): | 1232 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK): |
1297 return alt_target | 1233 return alt_target |
1298 return None | 1234 return None |
OLD | NEW |