bot_update/gclient: kill git fetch after timeout regardless of output.

Also spits out whatever output was produced by git fetch for debugging.

BUG=635641
R=machenbach@chromium.org,hinoka@chromium.org

Review-Url: https://codereview.chromium.org/2293013002
changes/18/399118/1
tandrii 9 years ago committed by Commit bot
parent 26a201f2a9
commit db8b839320

@ -466,13 +466,15 @@ class _KillTimer(object):
# TODO(tandrii): we really want to make use of subprocess42 here, and not # TODO(tandrii): we really want to make use of subprocess42 here, and not
# re-invent the wheel, but it's too much work :( # re-invent the wheel, but it's too much work :(
def __init__(self, timeout, child): def __init__(self, timeout, child, child_info):
self._timeout = timeout self._timeout = timeout
self._child = child self._child = child
self._child_info = child_info
self._cv = threading.Condition() self._cv = threading.Condition()
# All items below are protected by condition above. # All items below are protected by condition above.
self._kill_at = None self._kill_at = None
self._killing_attempted = False
self._working = True self._working = True
self._thread = None self._thread = None
@ -483,6 +485,10 @@ class _KillTimer(object):
self._thread.daemon = True self._thread.daemon = True
self._thread.start() self._thread.start()
@property
def killing_attempted(self):
return self._killing_attempted
def poke(self): def poke(self):
if not self._timeout: if not self._timeout:
return return
@ -506,8 +512,9 @@ class _KillTimer(object):
self._cv.wait(timeout=left) self._cv.wait(timeout=left)
continue continue
try: try:
logging.warn('killing child %s because of no output for %fs', logging.warn('killing child %s %s because of no output for %fs',
self._child, self._timeout) self._child.pid, self._child_info, self._timeout)
self._killing_attempted = True
self._child.kill() self._child.kill()
except OSError: except OSError:
logging.exception('failed to kill child %s', self._child) logging.exception('failed to kill child %s', self._child)
@ -528,9 +535,8 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
retry: If the process exits non-zero, sleep for a brief interval and try retry: If the process exits non-zero, sleep for a brief interval and try
again, up to RETRY_MAX times. again, up to RETRY_MAX times.
kill_timeout: (float) if given, number of seconds after which process would kill_timeout: (float) if given, number of seconds after which process would
be killed if there is no output. Must not be used with shell=True as be killed. Must not be used with shell=True as only shell process
only shell process would be killed, but not processes spawned by would be killed, but not processes spawned by shell.
shell.
stderr is always redirected to stdout. stderr is always redirected to stdout.
""" """
@ -543,6 +549,8 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
sleep_interval = RETRY_INITIAL_SLEEP sleep_interval = RETRY_INITIAL_SLEEP
run_cwd = kwargs.get('cwd', os.getcwd()) run_cwd = kwargs.get('cwd', os.getcwd())
debug_kid_info = "'%s' in %s" % (' '.join('"%s"' % x for x in args), run_cwd)
for _ in xrange(RETRY_MAX + 1): for _ in xrange(RETRY_MAX + 1):
kid = subprocess2.Popen( kid = subprocess2.Popen(
args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT, args, bufsize=0, stdout=subprocess2.PIPE, stderr=subprocess2.STDOUT,
@ -558,14 +566,13 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
# normally buffering is done for each line, but if svn requests input, no # normally buffering is done for each line, but if svn requests input, no
# end-of-line character is output after the prompt and it would not show up. # end-of-line character is output after the prompt and it would not show up.
try: try:
timeout_killer = _KillTimer(kill_timeout, kid) timeout_killer = _KillTimer(kill_timeout, kid, debug_kid_info)
in_byte = kid.stdout.read(1) in_byte = kid.stdout.read(1)
if in_byte: if in_byte:
if call_filter_on_first_line: if call_filter_on_first_line:
filter_fn(None) filter_fn(None)
in_line = '' in_line = ''
while in_byte: while in_byte:
timeout_killer.poke()
output.write(in_byte) output.write(in_byte)
if print_stdout: if print_stdout:
stdout.write(in_byte) stdout.write(in_byte)
@ -594,8 +601,12 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
return output.getvalue() return output.getvalue()
if not retry: if not retry:
break break
print ("WARNING: subprocess '%s' in %s failed; will retry after a short " print ("WARNING: subprocess %s failed; will retry after a short nap..." %
'nap...' % (' '.join('"%s"' % x for x in args), run_cwd)) debug_kid_info)
if timeout_killer.killing_attempted:
print('The subprocess above was likely killed because it looked hung. '
'Output thus far:\n> %s' %
('\n> '.join(output.getvalue().splitlines())))
time.sleep(sleep_interval) time.sleep(sleep_interval)
sleep_interval *= 2 sleep_interval *= 2
raise subprocess2.CalledProcessError( raise subprocess2.CalledProcessError(

@ -100,7 +100,7 @@ class CheckCallAndFilterTestCase(GclientUtilBase):
order.append('killed') order.append('killed')
cv.notify() cv.notify()
if kill_raises: if kill_raises:
raise OSError('somethign went wrong') raise OSError('something went wrong')
kid = self.ProcessIdMock('') kid = self.ProcessIdMock('')
kid.stdout.read = kid_stdout_read kid.stdout.read = kid_stdout_read

Loading…
Cancel
Save