devserver: Make symlink creation atomic.
Without this patch, the symlink creation is performed in two steps:
if the symlink already exists, it is first removed and then it
re-created. When two or more threads are creating the same symlink
in this wat, a race condition could make a thread attempt to create
a symlink that already exists because another thread created it
between the two steps. This situation is faced on the referenced bug.
On the other hand, a two-step symlink creation when the symlink
already exists leaves a small window of time where the symlink to
be replaced doesn't exists. This could make another thread trying
to read the symlink (either the previous or the new one) fail.
This patch fixes both problems by creating the symlink atomically.
This is done in a standard way, creating it somewhere else on the
same file system and moving it to its final destination.
BUG=chromium:270434
TEST=common_util unittest the symlink creation.
Change-Id: I38f24adcc5bf2f6d289105697de3981ecd51ad38
Reviewed-on: https://chromium-review.googlesource.com/167971
Reviewed-by: Alex Deymo <deymo@chromium.org>
Tested-by: Alex Deymo <deymo@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
diff --git a/autoupdate.py b/autoupdate.py
index 6bb2d06..d2fe95d 100644
--- a/autoupdate.py
+++ b/autoupdate.py
@@ -397,13 +397,6 @@
return cache_sub_dir
- def _Symlink(self, src_path, dest_path):
- if os.path.exists(src_path):
- if os.path.lexists(dest_path):
- os.unlink(dest_path)
- _Log('Creating symlink to: %s --> %s', dest_path, src_path)
- os.symlink(src_path, dest_path)
-
def _SymlinkUpdateFiles(self, image_dir):
"""Set files in the base static_dir to link to most recent update files.
@@ -420,7 +413,7 @@
for f in UPDATE_FILES:
link = os.path.join(self.static_dir, f)
target = os.path.join(image_dir, f)
- self._Symlink(target, link)
+ common_util.SymlinkFile(target, link)
def GetUpdateForLabel(self, client_version, label,
image_name=constants.TEST_IMAGE_FILE):
@@ -642,10 +635,10 @@
src_stateful = os.path.join(os.path.dirname(src_path),
constants.STATEFUL_FILE)
common_util.MkDirP(os.path.join(self.static_dir, label))
- self._Symlink(src_path, dest_path)
+ common_util.SymlinkFile(src_path, dest_path)
if os.path.exists(src_stateful):
# The stateful payload is optional.
- self._Symlink(src_stateful, dest_stateful)
+ common_util.SymlinkFile(src_stateful, dest_stateful)
else:
_Log('WARN: %s not found. Expected for dev and test builds',
constants.STATEFUL_FILE)