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

Side by Side Diff: gclient_utils.py

Issue 2410053002: Revert of bot_update/gclient: kill git fetch after timeout regardless of output. (Closed)
Patch Set: Created 4 years, 2 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
« no previous file with comments | « no previous file | tests/gclient_utils_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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, child_info): 469 def __init__(self, timeout, child):
470 self._timeout = timeout 470 self._timeout = timeout
471 self._child = child 471 self._child = child
472 self._child_info = child_info
473 472
474 self._cv = threading.Condition() 473 self._cv = threading.Condition()
475 # All items below are protected by condition above. 474 # All items below are protected by condition above.
476 self._kill_at = None 475 self._kill_at = None
477 self._killing_attempted = False
478 self._working = True 476 self._working = True
479 self._thread = None 477 self._thread = None
480 478
481 # Start the timer immediately. 479 # Start the timer immediately.
482 if self._timeout: 480 if self._timeout:
483 self._kill_at = time.time() + self._timeout 481 self._kill_at = time.time() + self._timeout
484 self._thread = threading.Thread(name='_KillTimer', target=self._work) 482 self._thread = threading.Thread(name='_KillTimer', target=self._work)
485 self._thread.daemon = True 483 self._thread.daemon = True
486 self._thread.start() 484 self._thread.start()
487 485
488 @property
489 def killing_attempted(self):
490 return self._killing_attempted
491
492 def poke(self): 486 def poke(self):
493 if not self._timeout: 487 if not self._timeout:
494 return 488 return
495 with self._cv: 489 with self._cv:
496 self._kill_at = time.time() + self._timeout 490 self._kill_at = time.time() + self._timeout
497 491
498 def cancel(self): 492 def cancel(self):
499 with self._cv: 493 with self._cv:
500 self._working = False 494 self._working = False
501 self._cv.notifyAll() 495 self._cv.notifyAll()
502 496
503 def _work(self): 497 def _work(self):
504 if not self._timeout: 498 if not self._timeout:
505 return 499 return
506 while True: 500 while True:
507 with self._cv: 501 with self._cv:
508 if not self._working: 502 if not self._working:
509 return 503 return
510 left = self._kill_at - time.time() 504 left = self._kill_at - time.time()
511 if left > 0: 505 if left > 0:
512 self._cv.wait(timeout=left) 506 self._cv.wait(timeout=left)
513 continue 507 continue
514 try: 508 try:
515 logging.warn('killing child %s %s because of no output for %fs', 509 logging.warn('killing child %s because of no output for %fs',
516 self._child.pid, self._child_info, self._timeout) 510 self._child, self._timeout)
517 self._killing_attempted = True
518 self._child.kill() 511 self._child.kill()
519 except OSError: 512 except OSError:
520 logging.exception('failed to kill child %s', self._child) 513 logging.exception('failed to kill child %s', self._child)
521 return 514 return
522 515
523 516
524 def CheckCallAndFilter(args, stdout=None, filter_fn=None, 517 def CheckCallAndFilter(args, stdout=None, filter_fn=None,
525 print_stdout=None, call_filter_on_first_line=False, 518 print_stdout=None, call_filter_on_first_line=False,
526 retry=False, kill_timeout=None, **kwargs): 519 retry=False, kill_timeout=None, **kwargs):
527 """Runs a command and calls back a filter function if needed. 520 """Runs a command and calls back a filter function if needed.
528 521
529 Accepts all subprocess2.Popen() parameters plus: 522 Accepts all subprocess2.Popen() parameters plus:
530 print_stdout: If True, the command's stdout is forwarded to stdout. 523 print_stdout: If True, the command's stdout is forwarded to stdout.
531 filter_fn: A function taking a single string argument called with each line 524 filter_fn: A function taking a single string argument called with each line
532 of the subprocess2's output. Each line has the trailing newline 525 of the subprocess2's output. Each line has the trailing newline
533 character trimmed. 526 character trimmed.
534 stdout: Can be any bufferable output. 527 stdout: Can be any bufferable output.
535 retry: If the process exits non-zero, sleep for a brief interval and try 528 retry: If the process exits non-zero, sleep for a brief interval and try
536 again, up to RETRY_MAX times. 529 again, up to RETRY_MAX times.
537 kill_timeout: (float) if given, number of seconds after which process would 530 kill_timeout: (float) if given, number of seconds after which process would
538 be killed. Must not be used with shell=True as only shell process 531 be killed if there is no output. Must not be used with shell=True as
539 would be killed, but not processes spawned by shell. 532 only shell process would be killed, but not processes spawned by
533 shell.
540 534
541 stderr is always redirected to stdout. 535 stderr is always redirected to stdout.
542 """ 536 """
543 assert print_stdout or filter_fn 537 assert print_stdout or filter_fn
544 assert not kwargs.get('shell', False) or not kill_timeout, ( 538 assert not kwargs.get('shell', False) or not kill_timeout, (
545 'kill_timeout should not be used with shell=True') 539 'kill_timeout should not be used with shell=True')
546 stdout = stdout or sys.stdout 540 stdout = stdout or sys.stdout
547 output = cStringIO.StringIO() 541 output = cStringIO.StringIO()
548 filter_fn = filter_fn or (lambda x: None) 542 filter_fn = filter_fn or (lambda x: None)
549 543
550 sleep_interval = RETRY_INITIAL_SLEEP 544 sleep_interval = RETRY_INITIAL_SLEEP
551 run_cwd = kwargs.get('cwd', os.getcwd()) 545 run_cwd = kwargs.get('cwd', os.getcwd())
552 debug_kid_info = "'%s' in %s" % (' '.join('"%s"' % x for x in args), run_cwd)
553
554 for _ in xrange(RETRY_MAX + 1): 546 for _ in xrange(RETRY_MAX + 1):
555 kid = subprocess2.Popen( 547 kid = subprocess2.Popen(
556 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, 548 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
557 **kwargs) 549 **kwargs)
558 550
559 GClientChildren.add(kid) 551 GClientChildren.add(kid)
560 552
561 # Do a flush of stdout before we begin reading from the subprocess2's stdout 553 # Do a flush of stdout before we begin reading from the subprocess2's stdout
562 stdout.flush() 554 stdout.flush()
563 555
564 # Also, we need to forward stdout to prevent weird re-ordering of output. 556 # Also, we need to forward stdout to prevent weird re-ordering of output.
565 # This has to be done on a per byte basis to make sure it is not buffered: 557 # This has to be done on a per byte basis to make sure it is not buffered:
566 # normally buffering is done for each line, but if svn requests input, no 558 # normally buffering is done for each line, but if svn requests input, no
567 # end-of-line character is output after the prompt and it would not show up. 559 # end-of-line character is output after the prompt and it would not show up.
568 try: 560 try:
569 timeout_killer = _KillTimer(kill_timeout, kid, debug_kid_info) 561 timeout_killer = _KillTimer(kill_timeout, kid)
570 in_byte = kid.stdout.read(1) 562 in_byte = kid.stdout.read(1)
571 if in_byte: 563 if in_byte:
572 if call_filter_on_first_line: 564 if call_filter_on_first_line:
573 filter_fn(None) 565 filter_fn(None)
574 in_line = '' 566 in_line = ''
575 while in_byte: 567 while in_byte:
568 timeout_killer.poke()
576 output.write(in_byte) 569 output.write(in_byte)
577 if print_stdout: 570 if print_stdout:
578 stdout.write(in_byte) 571 stdout.write(in_byte)
579 if in_byte not in ['\r', '\n']: 572 if in_byte not in ['\r', '\n']:
580 in_line += in_byte 573 in_line += in_byte
581 else: 574 else:
582 filter_fn(in_line) 575 filter_fn(in_line)
583 in_line = '' 576 in_line = ''
584 in_byte = kid.stdout.read(1) 577 in_byte = kid.stdout.read(1)
585 # Flush the rest of buffered output. This is only an issue with 578 # Flush the rest of buffered output. This is only an issue with
586 # stdout/stderr not ending with a \n. 579 # stdout/stderr not ending with a \n.
587 if len(in_line): 580 if len(in_line):
588 filter_fn(in_line) 581 filter_fn(in_line)
589 rv = kid.wait() 582 rv = kid.wait()
590 timeout_killer.cancel() 583 timeout_killer.cancel()
591 584
592 # Don't put this in a 'finally,' since the child may still run if we get 585 # Don't put this in a 'finally,' since the child may still run if we get
593 # an exception. 586 # an exception.
594 GClientChildren.remove(kid) 587 GClientChildren.remove(kid)
595 588
596 except KeyboardInterrupt: 589 except KeyboardInterrupt:
597 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) 590 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
598 raise 591 raise
599 592
600 if rv == 0: 593 if rv == 0:
601 return output.getvalue() 594 return output.getvalue()
602 if not retry: 595 if not retry:
603 break 596 break
604 print ("WARNING: subprocess %s failed; will retry after a short nap..." % 597 print ("WARNING: subprocess '%s' in %s failed; will retry after a short "
605 debug_kid_info) 598 'nap...' % (' '.join('"%s"' % x for x in args), run_cwd))
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())))
610 time.sleep(sleep_interval) 599 time.sleep(sleep_interval)
611 sleep_interval *= 2 600 sleep_interval *= 2
612 raise subprocess2.CalledProcessError( 601 raise subprocess2.CalledProcessError(
613 rv, args, kwargs.get('cwd', None), None, None) 602 rv, args, kwargs.get('cwd', None), None, None)
614 603
615 604
616 class GitFilter(object): 605 class GitFilter(object):
617 """A filter_fn implementation for quieting down git output messages. 606 """A filter_fn implementation for quieting down git output messages.
618 607
619 Allows a custom function to skip certain lines (predicate), and will throttle 608 Allows a custom function to skip certain lines (predicate), and will throttle
(...skipping 674 matching lines...) Expand 10 before | Expand all | Expand 10 after
1294 # Just incase we have some ~/blah paths. 1283 # Just incase we have some ~/blah paths.
1295 target = os.path.abspath(os.path.expanduser(target)) 1284 target = os.path.abspath(os.path.expanduser(target))
1296 if os.path.isfile(target) and os.access(target, os.X_OK): 1285 if os.path.isfile(target) and os.access(target, os.X_OK):
1297 return target 1286 return target
1298 if sys.platform.startswith('win'): 1287 if sys.platform.startswith('win'):
1299 for suffix in ('.bat', '.cmd', '.exe'): 1288 for suffix in ('.bat', '.cmd', '.exe'):
1300 alt_target = target + suffix 1289 alt_target = target + suffix
1301 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK): 1290 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
1302 return alt_target 1291 return alt_target
1303 return None 1292 return None
OLDNEW
« no previous file with comments | « no previous file | tests/gclient_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698