Commit ef509e48 authored by cyrille@nnamrak.org's avatar cyrille@nnamrak.org

Added SafeRename to better handle problems with git processes locking directories.

This solves a problem with "os.rename" sometimes failing with an
exception after cloning a dependency to a temporary directory. It's
possible the dying git processes still hold a handle to the directory.
Since gclient delete the temporary directory regardless of the success
of the process, it results in a lot of GB downloaded for nothing.

SafeRename solves this by retrying a few times if the renaming fails,
sleeping one second every time to get other processes the time to
release their lock on the directory. It gives up retrying after 15 times.

BUG=
R=maruel@chromium.org

Review URL: https://chromiumcodereview.appspot.com/23875041

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@224372 0039d316-1c4b-4281-b951-d872f2087c98
parent faf3fdf7
...@@ -873,8 +873,8 @@ class GitWrapper(SCMWrapper): ...@@ -873,8 +873,8 @@ class GitWrapper(SCMWrapper):
print(str(e)) print(str(e))
print('Retrying...') print('Retrying...')
gclient_utils.safe_makedirs(self.checkout_path) gclient_utils.safe_makedirs(self.checkout_path)
os.rename(os.path.join(tmp_dir, '.git'), gclient_utils.safe_rename(os.path.join(tmp_dir, '.git'),
os.path.join(self.checkout_path, '.git')) os.path.join(self.checkout_path, '.git'))
finally: finally:
if os.listdir(tmp_dir): if os.listdir(tmp_dir):
print('\n_____ removing non-empty tmp dir %s' % tmp_dir) print('\n_____ removing non-empty tmp dir %s' % tmp_dir)
......
...@@ -96,6 +96,29 @@ def FileWrite(filename, content, mode='w'): ...@@ -96,6 +96,29 @@ def FileWrite(filename, content, mode='w'):
f.write(content) f.write(content)
def safe_rename(old, new):
"""Renames a file reliably.
Sometimes os.rename does not work because a dying git process keeps a handle
on it for a few seconds. An exception is then thrown, which make the program
give up what it was doing and remove what was deleted.
The only solution is to catch the exception and try again until it works.
"""
# roughly 10s
retries = 100
for i in range(retries):
try:
os.rename(old, new)
break
except OSError:
if i == (retries - 1):
# Give up.
raise
# retry
logging.debug("Renaming failed from %s to %s. Retrying ..." % (old, new))
time.sleep(0.1)
def rmtree(path): def rmtree(path):
"""shutil.rmtree() on steroids. """shutil.rmtree() on steroids.
......
...@@ -38,8 +38,8 @@ class GclientUtilsUnittest(GclientUtilBase): ...@@ -38,8 +38,8 @@ class GclientUtilsUnittest(GclientUtilBase):
'GCLIENT_CHILDREN_LOCK', 'GClientChildren', 'GCLIENT_CHILDREN_LOCK', 'GClientChildren',
'SplitUrlRevision', 'SyntaxErrorToError', 'UpgradeToHttps', 'Wrapper', 'SplitUrlRevision', 'SyntaxErrorToError', 'UpgradeToHttps', 'Wrapper',
'WorkItem', 'codecs', 'lockedmethod', 'logging', 'os', 'pipes', 'Queue', 'WorkItem', 'codecs', 'lockedmethod', 'logging', 'os', 'pipes', 'Queue',
're', 'rmtree', 'safe_makedirs', 'stat', 'subprocess', 'subprocess2', 're', 'rmtree', 'safe_makedirs', 'safe_rename', 'stat', 'subprocess',
'sys', 'tempfile', 'threading', 'time', 'urlparse', 'subprocess2', 'sys', 'tempfile', 'threading', 'time', 'urlparse',
] ]
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
self.compareMembers(gclient_utils, members) self.compareMembers(gclient_utils, members)
......
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