diff options
author | Lukacs Berki <lberki@google.com> | 2016-01-20 14:04:18 +0000 |
---|---|---|
committer | Lukacs Berki <lberki@google.com> | 2016-01-20 14:11:06 +0000 |
commit | 42f67cb8f6a35d0e51d9d23871d077243af55da4 (patch) | |
tree | 943954ec2aac4cf436f4beea6107cef7282eaa88 | |
parent | 6921642556d260954209e607f12237008f193517 (diff) |
Add a Java property to influence symlinking strategy so that we can test what kind of performance we could get from how we imagine it would work under Windows.
--
MOS_MIGRATED_REVID=112572621
3 files changed, 128 insertions, 2 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java index 79792665d5..9474ea1196 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java @@ -1147,6 +1147,14 @@ public final class BlazeRuntime { workspaceDirectoryPath = fs.getPath(workspaceDirectory); } + if (fs instanceof UnixFileSystem) { + ((UnixFileSystem) fs).setRootsWithAllowedHardlinks( + // Some tests pass nulls for these paths, so remove these from the list + Iterables.filter( + Arrays.asList(installBasePath, outputBasePath, workspaceDirectoryPath), + Predicates.notNull())); + } + BlazeDirectories directories = new BlazeDirectories(installBasePath, outputBasePath, workspaceDirectoryPath, startupOptions.deepExecRoot, startupOptions.installMD5); diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java index ef9fa33ab6..9bb9555268 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java @@ -14,6 +14,7 @@ package com.google.devtools.build.lib.vfs; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.profiler.Profiler; @@ -25,6 +26,14 @@ import com.google.devtools.build.lib.unix.FilesystemUtils.ReadTypes; import com.google.devtools.build.lib.util.Preconditions; import java.io.IOException; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -36,8 +45,56 @@ import java.util.List; // Not final only for testing. @ThreadSafe public class UnixFileSystem extends AbstractFileSystemWithCustomStat { + /** + * What to do with requests to create symbolic links. + * + * Currently supports one value: SYMLINK, which simply calls symlink() . It obviously does not + * work on Windows. + */ + public enum SymlinkStrategy { + /** + * Use symlink(). Does not work on Windows, obviously. + */ + SYMLINK, + + /** + * Write a log message for symlinks that won't be compatible with how we are planning to pretend + * that they exist on Windows. + */ + WINDOWS_COMPATIBLE, + } + + private final SymlinkStrategy symlinkStrategy; + private final String symlinkLogFile; + + /** + * Directories where Bazel tries to hardlink files from instead of copying them. + * + * <p>These must be writable to the user. + */ + private ImmutableList<Path> rootsWithAllowedHardlinks; + + public UnixFileSystem() { + SymlinkStrategy symlinkStrategy = SymlinkStrategy.SYMLINK; + String strategyString = System.getProperty("io.bazel.SymlinkStrategy"); + symlinkLogFile = System.getProperty("io.bazel.SymlinkLogFile"); + if (strategyString != null && symlinkLogFile != null) { + try { + symlinkStrategy = SymlinkStrategy.valueOf(strategyString.toUpperCase()); + } catch (IllegalArgumentException e) { + // We just go with the default, this is just an experimental option so it's fine. + } + } + + this.symlinkStrategy = symlinkStrategy; + rootsWithAllowedHardlinks = ImmutableList.of(); + } + + // This method is a little ugly, but it's only for testing for now. + public void setRootsWithAllowedHardlinks(Iterable<Path> roots) { + this.rootsWithAllowedHardlinks = ImmutableList.copyOf(roots); + } - public static final UnixFileSystem INSTANCE = new UnixFileSystem(); /** * Eager implementation of FileStatus for file systems that have an atomic * stat(2) syscall. A proxy for {@link com.google.devtools.build.lib.unix.FileStatus}. @@ -311,11 +368,72 @@ public class UnixFileSystem extends AbstractFileSystemWithCustomStat { @Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException { + checkForWindowsCompatibility(linkPath, targetFragment); + synchronized (linkPath) { FilesystemUtils.symlink(targetFragment.toString(), linkPath.toString()); } } + private boolean isHardLinkAllowed(Path path) { + for (Path root : rootsWithAllowedHardlinks) { + if (path.startsWith(root)) { + return true; + } + } + + return false; + } + + private void emitSymlinkCompatibilityMessage( + String reason, Path linkPath, PathFragment targetFragment) + throws IOException { + // FileLock does not work for synchronization between threads in the same JVM as per its Javadoc + synchronized (symlinkLogFile) { + Exception e = new Exception(); + e.fillInStackTrace(); + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + String msg = String.format("ILLEGAL (%s): %s -> %s\nStack:\n%s", + reason, linkPath.getPathString(), targetFragment.getPathString(), sw.toString()); + + try (FileChannel channel = new RandomAccessFile(symlinkLogFile, "rwd").getChannel()) { + try (FileLock lock = channel.lock()) { + channel.position(channel.size()); + ByteBuffer data = Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(msg)); + channel.write(data); + } + } + } + } + + private void checkForWindowsCompatibility(Path linkPath, PathFragment targetFragment) + throws IOException { + if (symlinkStrategy != SymlinkStrategy.WINDOWS_COMPATIBLE) { + return; + } + + Path targetPath = linkPath.getRelative(targetFragment); + if (!targetPath.exists(Symlinks.FOLLOW)) { + // On Windows, one needs to know if the target is a directory or a file. + emitSymlinkCompatibilityMessage("Target does not exist", linkPath, targetFragment); + return; + } + + targetPath = targetPath.resolveSymbolicLinks(); + if (targetPath.isDirectory()) { + // We can use a junction + return; + } + + if (isHardLinkAllowed(targetPath)) { + // We can use a hard link + return; + } + + emitSymlinkCompatibilityMessage("Link to non-writable file", linkPath, targetFragment); + } + @Override protected PathFragment readSymbolicLink(Path path) throws IOException { // Note that the default implementation of readSymbolicLinkUnchecked calls this method and thus diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java index d9cce8b9c4..e3f1c9e478 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/FileFunctionTest.java @@ -1057,7 +1057,7 @@ public class FileFunctionTest { FileSystem oldFileSystem = Path.getFileSystemForSerialization(); try { - FileSystem fs = UnixFileSystem.INSTANCE; // InMemoryFS is not supported for serialization. + FileSystem fs = new UnixFileSystem(); // InMemoryFS is not supported for serialization. Path.setFileSystemForSerialization(fs); pkgRoot = fs.getRootDirectory(); |