mirror of https://github.com/axmolengine/axmol.git
165 lines
5.8 KiB
Python
165 lines
5.8 KiB
Python
#!/usr/bin/env python
|
|
# Copyright 2014 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
import logging
|
|
import optparse
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
import re
|
|
|
|
GIT_EXE="git"
|
|
GIT_TRANSIENT_ERRORS = (
|
|
# crbug.com/285832
|
|
r'!.*\[remote rejected\].*\(error in hook\)',
|
|
# crbug.com/289932
|
|
r'!.*\[remote rejected\].*\(failed to lock\)',
|
|
# crbug.com/307156
|
|
r'!.*\[remote rejected\].*\(error in Gerrit backend\)',
|
|
# crbug.com/285832
|
|
r'remote error: Internal Server Error',
|
|
# crbug.com/294449
|
|
r'fatal: Couldn\'t find remote ref ',
|
|
# crbug.com/220543
|
|
r'git fetch_pack: expected ACK/NAK, got',
|
|
# crbug.com/189455
|
|
r'protocol error: bad pack header',
|
|
# crbug.com/202807
|
|
r'The remote end hung up unexpectedly',
|
|
# crbug.com/298189
|
|
r'TLS packet with unexpected length was received',
|
|
# crbug.com/187444
|
|
r'RPC failed; result=\d+, HTTP code = \d+',
|
|
# crbug.com/388876
|
|
r'Connection timed out',
|
|
# crbug.com/430343
|
|
# TODO(dnj): Resync with Chromite.
|
|
r'The requested URL returned error: 5\d+',
|
|
)
|
|
GIT_TRANSIENT_ERRORS_RE = re.compile('|'.join(GIT_TRANSIENT_ERRORS),
|
|
re.IGNORECASE)
|
|
|
|
class TeeThread(threading.Thread):
|
|
def __init__(self, fd, out_fd, name):
|
|
super(TeeThread, self).__init__(name='git-retry.tee.%s' % (name,))
|
|
self.data = None
|
|
self.fd = fd
|
|
self.out_fd = out_fd
|
|
def run(self):
|
|
chunks = []
|
|
for line in self.fd:
|
|
chunks.append(line)
|
|
self.out_fd.write(line)
|
|
self.data = ''.join(chunks)
|
|
class GitRetry(object):
|
|
logger = logging.getLogger('git-retry')
|
|
DEFAULT_DELAY_SECS = 3.0
|
|
DEFAULT_RETRY_COUNT = 5
|
|
def __init__(self, retry_count=None, delay=None, delay_factor=None):
|
|
self.retry_count = retry_count or self.DEFAULT_RETRY_COUNT
|
|
self.delay = max(delay, 0) if delay else 0
|
|
self.delay_factor = max(delay_factor, 0) if delay_factor else 0
|
|
def shouldRetry(self, stderr):
|
|
m = GIT_TRANSIENT_ERRORS_RE.search(stderr)
|
|
if not m:
|
|
return False
|
|
self.logger.info("Encountered known transient error: [%s]",
|
|
stderr[m.start(): m.end()])
|
|
return True
|
|
@staticmethod
|
|
def execute(*args):
|
|
args = (GIT_EXE,) + args
|
|
proc = subprocess.Popen(
|
|
args,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
stderr_tee = TeeThread(proc.stderr, sys.stderr, 'stderr')
|
|
# Start our process. Collect/tee 'stdout' and 'stderr'.
|
|
stderr_tee.start()
|
|
try:
|
|
proc.wait()
|
|
except KeyboardInterrupt:
|
|
proc.kill()
|
|
raise
|
|
finally:
|
|
stderr_tee.join()
|
|
return proc.returncode, None, stderr_tee.data
|
|
def computeDelay(self, iteration):
|
|
"""Returns: the delay (in seconds) for a given iteration
|
|
The first iteration has a delay of '0'.
|
|
Args:
|
|
iteration: (int) The iteration index (starting with zero as the first
|
|
iteration)
|
|
"""
|
|
if (not self.delay) or (iteration == 0):
|
|
return 0
|
|
if self.delay_factor == 0:
|
|
# Linear delay
|
|
return iteration * self.delay
|
|
# Exponential delay
|
|
return (self.delay_factor ** (iteration - 1)) * self.delay
|
|
def __call__(self, *args):
|
|
returncode = 0
|
|
for i in xrange(self.retry_count):
|
|
# If the previous run failed and a delay is configured, delay before the
|
|
# next run.
|
|
delay = self.computeDelay(i)
|
|
if delay > 0:
|
|
self.logger.info("Delaying for [%s second(s)] until next retry", delay)
|
|
time.sleep(delay)
|
|
self.logger.debug("Executing subprocess (%d/%d) with arguments: %s",
|
|
(i+1), self.retry_count, args)
|
|
returncode, _, stderr = self.execute(*args)
|
|
self.logger.debug("Process terminated with return code: %d", returncode)
|
|
if returncode == 0:
|
|
break
|
|
if not self.shouldRetry(stderr):
|
|
self.logger.error("Process failure was not known to be transient; "
|
|
"terminating with return code %d", returncode)
|
|
break
|
|
return returncode
|
|
def main(args):
|
|
parser = optparse.OptionParser()
|
|
parser.disable_interspersed_args()
|
|
parser.add_option('-v', '--verbose',
|
|
action='count', default=0,
|
|
help="Increase verbosity; can be specified multiple times")
|
|
parser.add_option('-c', '--retry-count', metavar='COUNT',
|
|
type=int, default=GitRetry.DEFAULT_RETRY_COUNT,
|
|
help="Number of times to retry (default=%default)")
|
|
parser.add_option('-d', '--delay', metavar='SECONDS',
|
|
type=float, default=GitRetry.DEFAULT_DELAY_SECS,
|
|
help="Specifies the amount of time (in seconds) to wait "
|
|
"between successive retries (default=%default). This "
|
|
"can be zero.")
|
|
parser.add_option('-D', '--delay-factor', metavar='FACTOR',
|
|
type=int, default=2,
|
|
help="The exponential factor to apply to delays in between "
|
|
"successive failures (default=%default). If this is "
|
|
"zero, delays will increase linearly. Set this to "
|
|
"one to have a constant (non-increasing) delay.")
|
|
opts, args = parser.parse_args(args)
|
|
# Configure logging verbosity
|
|
if opts.verbose == 0:
|
|
logging.getLogger().setLevel(logging.WARNING)
|
|
elif opts.verbose == 1:
|
|
logging.getLogger().setLevel(logging.INFO)
|
|
else:
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
# Execute retries
|
|
retry = GitRetry(
|
|
retry_count=opts.retry_count,
|
|
delay=opts.delay,
|
|
delay_factor=opts.delay_factor,
|
|
)
|
|
return retry(*args)
|
|
if __name__ == '__main__':
|
|
logging.basicConfig()
|
|
logging.getLogger().setLevel(logging.WARNING)
|
|
try:
|
|
sys.exit(main(sys.argv[1:]))
|
|
except KeyboardInterrupt:
|
|
sys.stderr.write('interrupted\n')
|
|
sys.exit(1) |