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

Side by Side Diff: gclient_utils.py

Issue 2241843002: gclient: kill git fetch operation that hangs. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Nits. Created 4 years, 3 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 | « gclient_scm.py ('k') | tests/gclient_scm_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 441 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
462 def CheckCallAndFilter(args, stdout=None, filter_fn=None, 517 def CheckCallAndFilter(args, stdout=None, filter_fn=None,
463 print_stdout=None, call_filter_on_first_line=False, 518 print_stdout=None, call_filter_on_first_line=False,
464 retry=False, **kwargs): 519 retry=False, kill_timeout=None, **kwargs):
465 """Runs a command and calls back a filter function if needed. 520 """Runs a command and calls back a filter function if needed.
466 521
467 Accepts all subprocess2.Popen() parameters plus: 522 Accepts all subprocess2.Popen() parameters plus:
468 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.
469 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
470 of the subprocess2's output. Each line has the trailing newline 525 of the subprocess2's output. Each line has the trailing newline
471 character trimmed. 526 character trimmed.
472 stdout: Can be any bufferable output. 527 stdout: Can be any bufferable output.
473 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
474 again, up to RETRY_MAX times. 529 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.
475 534
476 stderr is always redirected to stdout. 535 stderr is always redirected to stdout.
477 """ 536 """
478 assert print_stdout or filter_fn 537 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')
479 stdout = stdout or sys.stdout 540 stdout = stdout or sys.stdout
480 output = cStringIO.StringIO() 541 output = cStringIO.StringIO()
481 filter_fn = filter_fn or (lambda x: None) 542 filter_fn = filter_fn or (lambda x: None)
482 543
483 sleep_interval = RETRY_INITIAL_SLEEP 544 sleep_interval = RETRY_INITIAL_SLEEP
484 run_cwd = kwargs.get('cwd', os.getcwd()) 545 run_cwd = kwargs.get('cwd', os.getcwd())
485 for _ in xrange(RETRY_MAX + 1): 546 for _ in xrange(RETRY_MAX + 1):
486 kid = subprocess2.Popen( 547 kid = subprocess2.Popen(
487 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, 548 args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
488 **kwargs) 549 **kwargs)
489 550
490 GClientChildren.add(kid) 551 GClientChildren.add(kid)
491 552
492 # 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
493 stdout.flush() 554 stdout.flush()
494 555
495 # 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.
496 # 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:
497 # 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
498 # 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.
499 try: 560 try:
561 timeout_killer = _KillTimer(kill_timeout, kid)
500 in_byte = kid.stdout.read(1) 562 in_byte = kid.stdout.read(1)
501 if in_byte: 563 if in_byte:
502 if call_filter_on_first_line: 564 if call_filter_on_first_line:
503 filter_fn(None) 565 filter_fn(None)
504 in_line = '' 566 in_line = ''
505 while in_byte: 567 while in_byte:
568 timeout_killer.poke()
506 output.write(in_byte) 569 output.write(in_byte)
507 if print_stdout: 570 if print_stdout:
508 stdout.write(in_byte) 571 stdout.write(in_byte)
509 if in_byte not in ['\r', '\n']: 572 if in_byte not in ['\r', '\n']:
510 in_line += in_byte 573 in_line += in_byte
511 else: 574 else:
512 filter_fn(in_line) 575 filter_fn(in_line)
513 in_line = '' 576 in_line = ''
514 in_byte = kid.stdout.read(1) 577 in_byte = kid.stdout.read(1)
515 # 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
516 # stdout/stderr not ending with a \n. 579 # stdout/stderr not ending with a \n.
517 if len(in_line): 580 if len(in_line):
518 filter_fn(in_line) 581 filter_fn(in_line)
519 rv = kid.wait() 582 rv = kid.wait()
583 timeout_killer.cancel()
520 584
521 # 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
522 # an exception. 586 # an exception.
523 GClientChildren.remove(kid) 587 GClientChildren.remove(kid)
524 588
525 except KeyboardInterrupt: 589 except KeyboardInterrupt:
526 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args) 590 print >> sys.stderr, 'Failed while running "%s"' % ' '.join(args)
527 raise 591 raise
528 592
529 if rv == 0: 593 if rv == 0:
(...skipping 695 matching lines...) Expand 10 before | Expand all | Expand 10 after
1225 # Just incase we have some ~/blah paths. 1289 # Just incase we have some ~/blah paths.
1226 target = os.path.abspath(os.path.expanduser(target)) 1290 target = os.path.abspath(os.path.expanduser(target))
1227 if os.path.isfile(target) and os.access(target, os.X_OK): 1291 if os.path.isfile(target) and os.access(target, os.X_OK):
1228 return target 1292 return target
1229 if sys.platform.startswith('win'): 1293 if sys.platform.startswith('win'):
1230 for suffix in ('.bat', '.cmd', '.exe'): 1294 for suffix in ('.bat', '.cmd', '.exe'):
1231 alt_target = target + suffix 1295 alt_target = target + suffix
1232 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK): 1296 if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
1233 return alt_target 1297 return alt_target
1234 return None 1298 return None
OLDNEW
« no previous file with comments | « gclient_scm.py ('k') | tests/gclient_scm_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698