|
|
|
@ -41,6 +41,42 @@ class LockError(Exception):
|
|
|
|
|
class ClobberNeeded(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def exponential_backoff_retry(fn, excs=(Exception,), name=None, count=10,
|
|
|
|
|
sleep_time=0.25, printerr=None):
|
|
|
|
|
"""Executes |fn| up to |count| times, backing off exponentially.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
fn (callable): The function to execute. If this raises a handled
|
|
|
|
|
exception, the function will retry with exponential backoff.
|
|
|
|
|
excs (tuple): A tuple of Exception types to handle. If one of these is
|
|
|
|
|
raised by |fn|, a retry will be attempted. If |fn| raises an Exception
|
|
|
|
|
that is not in this list, it will immediately pass through. If |excs|
|
|
|
|
|
is empty, the Exception base class will be used.
|
|
|
|
|
name (str): Optional operation name to print in the retry string.
|
|
|
|
|
count (int): The number of times to try before allowing the exception to
|
|
|
|
|
pass through.
|
|
|
|
|
sleep_time (float): The initial number of seconds to sleep in between
|
|
|
|
|
retries. This will be doubled each retry.
|
|
|
|
|
printerr (callable): Function that will be called with the error string upon
|
|
|
|
|
failures. If None, |logging.warning| will be used.
|
|
|
|
|
|
|
|
|
|
Returns: The return value of the successful fn.
|
|
|
|
|
"""
|
|
|
|
|
printerr = printerr or logging.warning
|
|
|
|
|
for i in xrange(count):
|
|
|
|
|
try:
|
|
|
|
|
return fn()
|
|
|
|
|
except excs as e:
|
|
|
|
|
if (i+1) >= count:
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
printerr('Retrying %s in %.2f second(s) (%d / %d attempts): %s' % (
|
|
|
|
|
(name or 'operation'), sleep_time, (i+1), count, e))
|
|
|
|
|
time.sleep(sleep_time)
|
|
|
|
|
sleep_time *= 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Lockfile(object):
|
|
|
|
|
"""Class to represent a cross-platform process-specific lockfile."""
|
|
|
|
|
|
|
|
|
@ -79,13 +115,16 @@ class Lockfile(object):
|
|
|
|
|
"""
|
|
|
|
|
if sys.platform == 'win32':
|
|
|
|
|
lockfile = os.path.normcase(self.lockfile)
|
|
|
|
|
for _ in xrange(3):
|
|
|
|
|
|
|
|
|
|
def delete():
|
|
|
|
|
exitcode = subprocess.call(['cmd.exe', '/c',
|
|
|
|
|
'del', '/f', '/q', lockfile])
|
|
|
|
|
if exitcode == 0:
|
|
|
|
|
return
|
|
|
|
|
time.sleep(3)
|
|
|
|
|
raise LockError('Failed to remove lock: %s' % lockfile)
|
|
|
|
|
if exitcode != 0:
|
|
|
|
|
raise LockError('Failed to remove lock: %s' % (lockfile,))
|
|
|
|
|
exponential_backoff_retry(
|
|
|
|
|
delete,
|
|
|
|
|
excs=(LockError,),
|
|
|
|
|
name='del [%s]' % (lockfile,))
|
|
|
|
|
else:
|
|
|
|
|
os.remove(self.lockfile)
|
|
|
|
|
|
|
|
|
@ -181,7 +220,7 @@ class Mirror(object):
|
|
|
|
|
else:
|
|
|
|
|
self.print = print
|
|
|
|
|
|
|
|
|
|
def print_without_file(self, message, **kwargs):
|
|
|
|
|
def print_without_file(self, message, **_kwargs):
|
|
|
|
|
self.print_func(message)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@ -230,6 +269,16 @@ class Mirror(object):
|
|
|
|
|
setattr(cls, 'cachepath', cachepath)
|
|
|
|
|
return getattr(cls, 'cachepath')
|
|
|
|
|
|
|
|
|
|
def Rename(self, src, dst):
|
|
|
|
|
# This is somehow racy on Windows.
|
|
|
|
|
# Catching OSError because WindowsError isn't portable and
|
|
|
|
|
# pylint complains.
|
|
|
|
|
exponential_backoff_retry(
|
|
|
|
|
lambda: os.rename(src, dst),
|
|
|
|
|
excs=(OSError,),
|
|
|
|
|
name='rename [%s] => [%s]' % (src, dst),
|
|
|
|
|
printerr=self.print)
|
|
|
|
|
|
|
|
|
|
def RunGit(self, cmd, **kwargs):
|
|
|
|
|
"""Run git in a subprocess."""
|
|
|
|
|
cwd = kwargs.setdefault('cwd', self.mirror_path)
|
|
|
|
@ -324,7 +373,15 @@ class Mirror(object):
|
|
|
|
|
retcode = 0
|
|
|
|
|
finally:
|
|
|
|
|
# Clean up the downloaded zipfile.
|
|
|
|
|
gclient_utils.rm_file_or_tree(tempdir)
|
|
|
|
|
#
|
|
|
|
|
# This is somehow racy on Windows.
|
|
|
|
|
# Catching OSError because WindowsError isn't portable and
|
|
|
|
|
# pylint complains.
|
|
|
|
|
exponential_backoff_retry(
|
|
|
|
|
lambda: gclient_utils.rm_file_or_tree(tempdir),
|
|
|
|
|
excs=(OSError,),
|
|
|
|
|
name='rmtree [%s]' % (tempdir,),
|
|
|
|
|
printerr=self.print)
|
|
|
|
|
|
|
|
|
|
if retcode:
|
|
|
|
|
self.print(
|
|
|
|
@ -441,7 +498,7 @@ class Mirror(object):
|
|
|
|
|
if tempdir:
|
|
|
|
|
if os.path.exists(self.mirror_path):
|
|
|
|
|
gclient_utils.rmtree(self.mirror_path)
|
|
|
|
|
os.rename(tempdir, self.mirror_path)
|
|
|
|
|
self.Rename(tempdir, self.mirror_path)
|
|
|
|
|
if not ignore_lock:
|
|
|
|
|
lockfile.unlock()
|
|
|
|
|
|
|
|
|
|