Commit 4625b5ad authored by dnj's avatar dnj Committed by Commit bot

Add retries to file operations for Windows.

BUG=chromium:663990
TEST=None
R=hinoka@chromium.org, nodir@chromium.org

Review-Url: https://codereview.chromium.org/2497503002
parent b445ef56
...@@ -41,6 +41,42 @@ class LockError(Exception): ...@@ -41,6 +41,42 @@ class LockError(Exception):
class ClobberNeeded(Exception): class ClobberNeeded(Exception):
pass 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 Lockfile(object):
"""Class to represent a cross-platform process-specific lockfile.""" """Class to represent a cross-platform process-specific lockfile."""
...@@ -79,13 +115,16 @@ class Lockfile(object): ...@@ -79,13 +115,16 @@ class Lockfile(object):
""" """
if sys.platform == 'win32': if sys.platform == 'win32':
lockfile = os.path.normcase(self.lockfile) lockfile = os.path.normcase(self.lockfile)
for _ in xrange(3):
def delete():
exitcode = subprocess.call(['cmd.exe', '/c', exitcode = subprocess.call(['cmd.exe', '/c',
'del', '/f', '/q', lockfile]) 'del', '/f', '/q', lockfile])
if exitcode == 0: if exitcode != 0:
return raise LockError('Failed to remove lock: %s' % (lockfile,))
time.sleep(3) exponential_backoff_retry(
raise LockError('Failed to remove lock: %s' % lockfile) delete,
excs=(LockError,),
name='del [%s]' % (lockfile,))
else: else:
os.remove(self.lockfile) os.remove(self.lockfile)
...@@ -181,7 +220,7 @@ class Mirror(object): ...@@ -181,7 +220,7 @@ class Mirror(object):
else: else:
self.print = print self.print = print
def print_without_file(self, message, **kwargs): def print_without_file(self, message, **_kwargs):
self.print_func(message) self.print_func(message)
@property @property
...@@ -230,6 +269,16 @@ class Mirror(object): ...@@ -230,6 +269,16 @@ class Mirror(object):
setattr(cls, 'cachepath', cachepath) setattr(cls, 'cachepath', cachepath)
return getattr(cls, '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): def RunGit(self, cmd, **kwargs):
"""Run git in a subprocess.""" """Run git in a subprocess."""
cwd = kwargs.setdefault('cwd', self.mirror_path) cwd = kwargs.setdefault('cwd', self.mirror_path)
...@@ -324,7 +373,15 @@ class Mirror(object): ...@@ -324,7 +373,15 @@ class Mirror(object):
retcode = 0 retcode = 0
finally: finally:
# Clean up the downloaded zipfile. # 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: if retcode:
self.print( self.print(
...@@ -441,7 +498,7 @@ class Mirror(object): ...@@ -441,7 +498,7 @@ class Mirror(object):
if tempdir: if tempdir:
if os.path.exists(self.mirror_path): if os.path.exists(self.mirror_path):
gclient_utils.rmtree(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: if not ignore_lock:
lockfile.unlock() lockfile.unlock()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment