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

Unified 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, 4 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « gclient_scm.py ('k') | tests/gclient_scm_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: gclient_utils.py
diff --git a/gclient_utils.py b/gclient_utils.py
index a988b824261132bca0ea9d7ff905daeacb96209b..ca4d2e400a3ad896d090951b90eb570b8a80f111 100644
--- a/gclient_utils.py
+++ b/gclient_utils.py
@@ -459,9 +459,64 @@ class GClientChildren(object):
print >> sys.stderr, ' ', zombie.pid
+class _KillTimer(object):
+ """Timer that kills child process after certain interval since last poke or
+ creation.
+ """
+ # TODO(tandrii): we really want to make use of subprocess42 here, and not
+ # re-invent the wheel, but it's too much work :(
+
+ def __init__(self, timeout, child):
+ self._timeout = timeout
+ self._child = child
+
+ self._cv = threading.Condition()
+ # All items below are protected by condition above.
+ self._kill_at = None
+ self._working = True
+ self._thread = None
+
+ # Start the timer immediately.
+ if self._timeout:
+ self._kill_at = time.time() + self._timeout
+ self._thread = threading.Thread(name='_KillTimer', target=self._work)
+ self._thread.daemon = True
+ self._thread.start()
+
+ def poke(self):
+ if not self._timeout:
+ return
+ with self._cv:
+ self._kill_at = time.time() + self._timeout
+
+ def cancel(self):
+ with self._cv:
+ self._working = False
+ self._cv.notifyAll()
+
+ def _work(self):
+ if not self._timeout:
+ return
+ while True:
+ with self._cv:
+ if not self._working:
+ return
+ left = self._kill_at - time.time()
+ if left > 0:
+ self._cv.wait(timeout=left)
+ continue
+ try:
+ logging.warn('killing child %s because of no output for %fs',
+ self._child, self._timeout)
+ self._child.kill()
+ except OSError:
+ logging.exception('failed to kill child %s', self._child)
+ return
+
+
def CheckCallAndFilter(args, stdout=None, filter_fn=None,
print_stdout=None, call_filter_on_first_line=False,
- retry=False, **kwargs):
+ retry=False, kill_timeout=None, **kwargs):
"""Runs a command and calls back a filter function if needed.
Accepts all subprocess2.Popen() parameters plus:
@@ -472,10 +527,16 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
stdout: Can be any bufferable output.
retry: If the process exits non-zero, sleep for a brief interval and try
again, up to RETRY_MAX times.
+ 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
+ only shell process would be killed, but not processes spawned by
+ shell.
stderr is always redirected to stdout.
"""
assert print_stdout or filter_fn
+ assert not kwargs.get('shell', False) or not kill_timeout, (
+ 'kill_timeout should not be used with shell=True')
stdout = stdout or sys.stdout
output = cStringIO.StringIO()
filter_fn = filter_fn or (lambda x: None)
@@ -497,12 +558,14 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
# 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.
try:
+ timeout_killer = _KillTimer(kill_timeout, kid)
in_byte = kid.stdout.read(1)
if in_byte:
if call_filter_on_first_line:
filter_fn(None)
in_line = ''
while in_byte:
+ timeout_killer.poke()
output.write(in_byte)
if print_stdout:
stdout.write(in_byte)
@@ -517,6 +580,7 @@ def CheckCallAndFilter(args, stdout=None, filter_fn=None,
if len(in_line):
filter_fn(in_line)
rv = kid.wait()
+ timeout_killer.cancel()
# Don't put this in a 'finally,' since the child may still run if we get
# an exception.
« 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