Replace UnixGlob's package glob prefetching functionality with an
optional method in FileSystem. Custom FileSystem implementations can
use this to provide their own implementation of glob prefetching.
--
MOS_MIGRATED_REVID=140736304
diff --git a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
index 5259385..56df73c 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
@@ -138,16 +138,8 @@
Future<List<Path>> cached = globCache.get(Pair.of(pattern, excludeDirs));
if (cached == null) {
if (maxDirectoriesToEagerlyVisit > -1
- && !globalStarted.getAndSet(true)
- && !pattern.startsWith("**")) {
- UnixGlob.forPath(packageDirectory)
- .setMaxDirectoriesToEagerlyVisit(maxDirectoriesToEagerlyVisit)
- .addPattern("**")
- .setExcludeDirectories(true)
- .setDirectoryFilter(childDirectoryPredicate)
- .setThreadPool(globExecutor)
- .setFilesystemCalls(syscalls)
- .globAsync(true);
+ && !globalStarted.getAndSet(true)) {
+ packageDirectory.prefetchPackageAsync(maxDirectoriesToEagerlyVisit);
}
cached = safeGlobUnsorted(pattern, excludeDirs);
setGlobPaths(pattern, excludeDirs, cached);
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
index 4d93804..de1fe986 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -827,4 +827,12 @@
*/
protected abstract void createFSDependentHardLink(Path linkPath, Path originalPath)
throws IOException;
+
+ /**
+ * Prefetch all directories and symlinks within the package
+ * rooted at "path". Enter at most "maxDirs" total directories.
+ * Specializations for high-latency remote filesystems may wish to
+ * implement this in order to warm the filesystem's internal caches.
+ */
+ protected void prefetchPackageAsync(Path path, int maxDirs) { }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
index e039efe..cdc0a0a 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
@@ -1227,6 +1227,10 @@
fileSystem.chmod(this, mode);
}
+ public void prefetchPackageAsync(int maxDirs) {
+ fileSystem.prefetchPackageAsync(this, maxDirs);
+ }
+
/**
* Compare Paths of the same file system using their PathFragments.
*
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
index 82b5e55..9446aa3 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
@@ -44,7 +44,6 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
@@ -71,7 +70,7 @@
GlobVisitor visitor =
(threadPool == null)
? new GlobVisitor(checkForInterruption)
- : new GlobVisitor(threadPool, checkForInterruption, -1);
+ : new GlobVisitor(threadPool, checkForInterruption);
return visitor.glob(base, patterns, excludeDirectories, dirPred, syscalls);
}
@@ -85,7 +84,7 @@
GlobVisitor visitor =
(threadPool == null)
? new GlobVisitor(checkForInterruption)
- : new GlobVisitor(threadPool, checkForInterruption, -1);
+ : new GlobVisitor(threadPool, checkForInterruption);
visitor.glob(base, patterns, excludeDirectories, dirPred, syscalls);
return visitor.getNumGlobTasksForTesting();
}
@@ -97,10 +96,9 @@
Predicate<Path> dirPred,
FilesystemCalls syscalls,
boolean checkForInterruption,
- ThreadPoolExecutor threadPool,
- int maxDirectoriesToEagerlyVisit) {
+ ThreadPoolExecutor threadPool) {
Preconditions.checkNotNull(threadPool, "%s %s", base, patterns);
- return new GlobVisitor(threadPool, checkForInterruption, maxDirectoriesToEagerlyVisit)
+ return new GlobVisitor(threadPool, checkForInterruption)
.globAsync(base, patterns, excludeDirectories, dirPred, syscalls);
}
@@ -309,7 +307,6 @@
private ThreadPoolExecutor threadPool;
private AtomicReference<? extends FilesystemCalls> syscalls =
new AtomicReference<>(DEFAULT_SYSCALLS);
- private int maxDirectoriesToEagerlyVisit = -1;
/**
* Creates a glob builder with the given base path.
@@ -390,11 +387,6 @@
return this;
}
- public Builder setMaxDirectoriesToEagerlyVisit(int maxDirectoriesToEagerlyVisit) {
- this.maxDirectoriesToEagerlyVisit = maxDirectoriesToEagerlyVisit;
- return this;
- }
-
/**
* Executes the glob.
*/
@@ -439,8 +431,7 @@
pathFilter,
syscalls.get(),
checkForInterrupt,
- threadPool,
- maxDirectoriesToEagerlyVisit);
+ threadPool);
}
}
@@ -507,21 +498,17 @@
private final AtomicLong totalOps = new AtomicLong(0);
private final AtomicLong pendingOps = new AtomicLong(0);
private final AtomicReference<IOException> failure = new AtomicReference<>();
- private final int maxDirectoriesToEagerlyVisit;
- private final AtomicInteger visitedDirectories = new AtomicInteger(0);
private volatile boolean canceled = false;
GlobVisitor(
ThreadPoolExecutor executor,
- boolean failFastOnInterrupt,
- int maxDirectoriesToEagerlyVisit) {
+ boolean failFastOnInterrupt) {
this.executor = executor;
this.result = new GlobFuture(this, failFastOnInterrupt);
- this.maxDirectoriesToEagerlyVisit = maxDirectoriesToEagerlyVisit;
}
GlobVisitor(boolean failFastOnInterrupt) {
- this(null, failFastOnInterrupt, -1);
+ this(null, failFastOnInterrupt);
}
/**
@@ -558,14 +545,6 @@
}
/**
- * Whether or not to store the results of this glob. If this glob is being done purely to warm
- * the filesystem, we do not store the results, since it would take unnecessary memory.
- */
- private boolean storeGlobResults() {
- return maxDirectoriesToEagerlyVisit == -1;
- }
-
- /**
* Same as {@link #glob}, except does so asynchronously and returns a {@link Future} for the
* result.
*/
@@ -780,7 +759,7 @@
}
if (idx == context.patternParts.length) { // Base case.
- if (storeGlobResults() && !(context.excludeDirectories && baseIsDir)) {
+ if (!(context.excludeDirectories && baseIsDir)) {
results.add(base);
}
@@ -792,10 +771,6 @@
return;
}
- if (maxDirectoriesToEagerlyVisit > -1
- && visitedDirectories.incrementAndGet() > maxDirectoriesToEagerlyVisit) {
- return;
- }
final String pattern = context.patternParts[idx];
// ** is special: it can match nothing at all.
@@ -843,7 +818,7 @@
context.queueGlob(child, childIsDir, idx + 1);
} else {
// Instead of using an async call, just repeat the base case above.
- if (storeGlobResults() && idx + 1 == context.patternParts.length) {
+ if (idx + 1 == context.patternParts.length) {
results.add(child);
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
index 3c960df..58321cc 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
@@ -24,6 +24,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
@@ -35,7 +36,6 @@
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
import com.google.devtools.build.lib.testutil.ManualClock;
-import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.util.BlazeClock;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.Dirent;
@@ -56,10 +56,8 @@
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -760,255 +758,74 @@
}
}
- @Test
- public void testGlobsHappenInParallel() throws Exception {
- scratch.file(
- "foo/BUILD",
- "load('//foo:my_library.bzl', 'my_library')",
- "[sh_library(name = x + '-matched') for x in glob(['bar/*'], exclude_directories = 0)]",
- "cc_library(name = 'cc', srcs = glob(['cc/*']))",
- "my_library(name = 'my', srcs = glob(['sh/*']))");
- scratch.file(
- "foo/my_library.bzl",
- "def my_library(name = None, srcs = []):",
- " native.sh_library(name = name, srcs = srcs, deps = native.glob(['inner/*']))");
- scratch.file("foo/bar/1");
- Path barPath = scratch.file("foo/bar/2").getParentDirectory();
- Path ccPath = scratch.file("foo/cc/src.file").getParentDirectory();
- Path shPath = scratch.dir("foo/sh");
- Path innerPath = scratch.dir("foo/inner");
- PackageCacheOptions packageCacheOptions = Options.getDefaults(PackageCacheOptions.class);
- packageCacheOptions.defaultVisibility = ConstantRuleVisibility.PUBLIC;
- packageCacheOptions.showLoadingProgress = true;
- packageCacheOptions.globbingThreads = 7;
- packageCacheOptions.maxDirectoriesToEagerlyVisitInGlobbing = 10;
- getSkyframeExecutor()
- .preparePackageLoading(
- new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)),
- packageCacheOptions,
- "",
- UUID.randomUUID(),
- ImmutableMap.<String, String>of(),
- new TimestampGranularityMonitor(BlazeClock.instance()));
-
- SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("@//foo"));
- final CountDownLatch allDirsRequested = new CountDownLatch(4);
- Listener synchronizeListener =
- new Listener() {
- @Override
- public Object accept(Path path, FileOp op, Order order) throws IOException {
- if (op == FileOp.READDIR && order == Order.BEFORE) {
- allDirsRequested.countDown();
- try {
- assertThat(
- allDirsRequested.await(
- TestUtils.WAIT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS))
- .isTrue();
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- }
- return NO_RESULT_MARKER;
- }
- };
- fs.setCustomOverride(barPath, synchronizeListener);
- fs.setCustomOverride(ccPath, synchronizeListener);
- fs.setCustomOverride(shPath, synchronizeListener);
- fs.setCustomOverride(innerPath, synchronizeListener);
- PackageValue value = validPackage(skyKey);
- assertFalse(value.getPackage().containsErrors());
- assertThat(value.getPackage().getTarget("bar/1-matched").getName()).isEqualTo("bar/1-matched");
- assertThat(value.getPackage().getTarget("cc/src.file")).isNotNull();
- assertThat(
- (Iterable<?>)
- value
- .getPackage()
- .getTarget("my")
- .getAssociatedRule()
- .getAttributeContainer()
- .getAttr("srcs"))
- .isEmpty();
- }
-
- @Test
- public void testGlobsDontHappenInParallel() throws Exception {
- scratch.file(
- "foo/BUILD",
- "load('//foo:my_library.bzl', 'my_library')",
- "[sh_library(name = x + '-matched') for x in glob(['bar/*'], exclude_directories = 0)]",
- "cc_library(name = 'cc', srcs = glob(['cc/*']))",
- "my_library(name = 'my', srcs = glob(['sh/*']))");
- scratch.file(
- "foo/my_library.bzl",
- "def my_library(name = None, srcs = []):",
- " native.sh_library(name = name, srcs = srcs, deps = native.glob(['inner/*']))");
- scratch.file("foo/bar/1");
- Path barPath = scratch.file("foo/bar/2").getParentDirectory();
- Path ccPath = scratch.file("foo/cc/src.file").getParentDirectory();
- Path shPath = scratch.dir("foo/sh");
- Path innerPath = scratch.dir("foo/inner");
- PackageCacheOptions packageCacheOptions = Options.getDefaults(PackageCacheOptions.class);
- packageCacheOptions.defaultVisibility = ConstantRuleVisibility.PUBLIC;
- packageCacheOptions.showLoadingProgress = true;
- packageCacheOptions.globbingThreads = 7;
- packageCacheOptions.maxDirectoriesToEagerlyVisitInGlobbing = -1;
- getSkyframeExecutor()
- .preparePackageLoading(
- new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)),
- packageCacheOptions,
- "",
- UUID.randomUUID(),
- ImmutableMap.<String, String>of(),
- new TimestampGranularityMonitor(BlazeClock.instance()));
-
- SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("@//foo"));
- final AtomicBoolean atLeastOneUnfinishedRequest = new AtomicBoolean(false);
- final CountDownLatch allDirsRequested = new CountDownLatch(4);
- Listener synchronizeListener =
- new Listener() {
- @Override
- public Object accept(Path path, FileOp op, Order order) throws IOException {
- if (op == FileOp.READDIR && order == Order.BEFORE) {
- allDirsRequested.countDown();
- try {
- if (!allDirsRequested.await(1, TimeUnit.SECONDS)) {
- atLeastOneUnfinishedRequest.set(true);
- }
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- }
- return NO_RESULT_MARKER;
- }
- };
- fs.setCustomOverride(barPath, synchronizeListener);
- fs.setCustomOverride(ccPath, synchronizeListener);
- fs.setCustomOverride(shPath, synchronizeListener);
- fs.setCustomOverride(innerPath, synchronizeListener);
- PackageValue value = validPackage(skyKey);
- assertFalse(value.getPackage().containsErrors());
- assertThat(value.getPackage().getTarget("bar/1-matched").getName()).isEqualTo("bar/1-matched");
- assertThat(value.getPackage().getTarget("cc/src.file")).isNotNull();
- assertThat(
- (Iterable<?>)
- value
- .getPackage()
- .getTarget("my")
- .getAssociatedRule()
- .getAttributeContainer()
- .getAttr("srcs"))
- .isEmpty();
- assertThat(atLeastOneUnfinishedRequest.get()).isTrue();
- }
-
private static class CustomInMemoryFs extends InMemoryFileSystem {
- private final Map<Path, Listener> customOverrides = Maps.newHashMap();
+ private abstract static class FileStatusOrException {
+ abstract FileStatus get() throws IOException;
+
+ private static class ExceptionImpl extends FileStatusOrException {
+ private final IOException exn;
+
+ private ExceptionImpl(IOException exn) {
+ this.exn = exn;
+ }
+
+ @Override
+ FileStatus get() throws IOException {
+ throw exn;
+ }
+ }
+
+ private static class FileStatusImpl extends FileStatusOrException {
+
+ @Nullable
+ private final FileStatus fileStatus;
+
+ private FileStatusImpl(@Nullable FileStatus fileStatus) {
+ this.fileStatus = fileStatus;
+ }
+
+ @Override
+ @Nullable
+ FileStatus get() {
+ return fileStatus;
+ }
+ }
+ }
+
+ private Map<Path, FileStatusOrException> stubbedStats = Maps.newHashMap();
+ private Set<Path> makeUnreadableAfterReaddir = Sets.newHashSet();
public CustomInMemoryFs(ManualClock manualClock) {
super(manualClock);
}
- public void stubStat(final Path targetPath, @Nullable final FileStatus stubbedResult) {
- setCustomOverride(
- targetPath,
- new Listener() {
- @Override
- public Object accept(Path path, FileOp op, Order order) {
- if (targetPath.equals(path) && op == FileOp.STAT && order == Order.BEFORE) {
- return stubbedResult;
- } else {
- return NO_RESULT_MARKER;
- }
- }
- });
+ public void stubStat(Path path, @Nullable FileStatus stubbedResult) {
+ stubbedStats.put(path, new FileStatusOrException.FileStatusImpl(stubbedResult));
}
- public void stubStatError(final Path targetPath, final IOException stubbedResult) {
- setCustomOverride(
- targetPath,
- new Listener() {
- @Override
- public Object accept(Path path, FileOp op, Order order) throws IOException {
- if (targetPath.equals(path) && op == FileOp.STAT && order == Order.BEFORE) {
- throw stubbedResult;
- } else {
- return NO_RESULT_MARKER;
- }
- }
- });
- }
-
- void setCustomOverride(Path path, Listener listener) {
- customOverrides.put(path, listener);
+ public void stubStatError(Path path, IOException stubbedResult) {
+ stubbedStats.put(path, new FileStatusOrException.ExceptionImpl(stubbedResult));
}
@Override
public FileStatus stat(Path path, boolean followSymlinks) throws IOException {
- Listener listener = customOverrides.get(path);
- if (listener != null) {
- Object status = listener.accept(path, FileOp.STAT, Order.BEFORE);
- if (status != NO_RESULT_MARKER) {
- return (FileStatus) status;
- }
+ if (stubbedStats.containsKey(path)) {
+ return stubbedStats.get(path).get();
}
- FileStatus fileStatus = super.stat(path, followSymlinks);
- if (listener != null) {
- Object status = listener.accept(path, FileOp.STAT, Order.AFTER);
- if (status != NO_RESULT_MARKER) {
- return (FileStatus) status;
- }
- }
- return fileStatus;
+ return super.stat(path, followSymlinks);
}
- public void scheduleMakeUnreadableAfterReaddir(final Path targetPath) {
- setCustomOverride(
- targetPath,
- new Listener() {
- @Override
- public Object accept(Path path, FileOp op, Order order) throws IOException {
- if (targetPath.equals(path) && op == FileOp.READDIR && order == Order.AFTER) {
- targetPath.setReadable(false);
- }
- return NO_RESULT_MARKER;
- }
- });
+ public void scheduleMakeUnreadableAfterReaddir(Path path) {
+ makeUnreadableAfterReaddir.add(path);
}
- @SuppressWarnings("unchecked")
@Override
public Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException {
- Listener listener = customOverrides.get(path);
- if (listener != null) {
- Object status = listener.accept(path, FileOp.READDIR, Order.BEFORE);
- if (status != NO_RESULT_MARKER) {
- return (Collection<Dirent>) status;
- }
- }
Collection<Dirent> result = super.readdir(path, followSymlinks);
- if (listener != null) {
- Object status = listener.accept(path, FileOp.READDIR, Order.AFTER);
- if (status != NO_RESULT_MARKER) {
- return (Collection<Dirent>) status;
- }
+ if (makeUnreadableAfterReaddir.contains(path)) {
+ path.setReadable(false);
}
return result;
}
}
-
- private static final Object NO_RESULT_MARKER = new Object();
-
- private enum Order {
- BEFORE,
- AFTER
- }
-
- private enum FileOp {
- STAT,
- READDIR
- }
-
- private interface Listener {
- Object accept(Path path, FileOp op, Order order) throws IOException;
- }
}