// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.skyframe; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.Dirent; import com.google.devtools.build.lib.vfs.FileStatus; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.Symlinks; import com.google.devtools.build.lib.vfs.UnixGlob; import java.io.IOException; import java.util.Collection; /** * A per-build cache of filesystem operations for Skyframe invocations of legacy package loading. */ public class PerBuildSyscallCache implements UnixGlob.FilesystemCalls { private final LoadingCache<Pair<Path, Symlinks>, Pair<FileStatus, IOException>> statCache; private final LoadingCache<Pair<Path, Symlinks>, Pair<Collection<Dirent>, IOException>> readdirCache; private static final FileStatus NO_STATUS = new FakeFileStatus(); private PerBuildSyscallCache( LoadingCache<Pair<Path, Symlinks>, Pair<FileStatus, IOException>> statCache, LoadingCache<Pair<Path, Symlinks>, Pair<Collection<Dirent>, IOException>> readdirCache) { this.statCache = statCache; this.readdirCache = readdirCache; } public static Builder newBuilder() { return new Builder(); } /** Builder for a per-build filesystem cache. */ public static class Builder { private static final int UNSET = -1; private int maxStats = UNSET; private int maxReaddirs = UNSET; private int concurrencyLevel = UNSET; private Builder() { } /** Sets the upper bound of the 'stat' cache. This cache is unbounded by default. */ public Builder setMaxStats(int maxStats) { this.maxStats = maxStats; return this; } /** Sets the upper bound of the 'readdir' cache. This cache is unbounded by default. */ public Builder setMaxReaddirs(int maxReaddirs) { this.maxReaddirs = maxReaddirs; return this; } /** Sets the concurrency level of the caches. */ public Builder setConcurrencyLevel(int concurrencyLevel) { this.concurrencyLevel = concurrencyLevel; return this; } public PerBuildSyscallCache build() { CacheBuilder<Object, Object> statCacheBuilder = CacheBuilder.newBuilder(); if (maxStats != UNSET) { statCacheBuilder = statCacheBuilder.maximumSize(maxStats); } CacheBuilder<Object, Object> readdirCacheBuilder = CacheBuilder.newBuilder(); if (maxReaddirs != UNSET) { readdirCacheBuilder = readdirCacheBuilder.maximumSize(maxStats); } if (concurrencyLevel != UNSET) { statCacheBuilder = statCacheBuilder.concurrencyLevel(concurrencyLevel); readdirCacheBuilder = readdirCacheBuilder.concurrencyLevel(concurrencyLevel); } return new PerBuildSyscallCache(statCacheBuilder.build(newStatLoader()), readdirCacheBuilder.build(newReaddirLoader())); } } @Override public Collection<Dirent> readdir(Path path, Symlinks symlinks) throws IOException { Pair<Collection<Dirent>, IOException> result = readdirCache.getUnchecked(Pair.of(path, symlinks)); Collection<Dirent> entries = result.getFirst(); if (entries != null) { return entries; } throw result.getSecond(); } @Override public FileStatus statIfFound(Path path, Symlinks symlinks) throws IOException { Pair<FileStatus, IOException> status = statCache.getUnchecked(Pair.of(path, symlinks)); if (status.getFirst() != null) { return (status.getFirst() == NO_STATUS) ? null : status.getFirst(); } throw status.getSecond(); } public void clear() { statCache.invalidateAll(); readdirCache.invalidateAll(); } // This is used because the cache implementations don't allow null. private static final class FakeFileStatus implements FileStatus { @Override public long getLastChangeTime() { throw new UnsupportedOperationException(); } @Override public long getNodeId() { throw new UnsupportedOperationException(); } @Override public long getLastModifiedTime() { throw new UnsupportedOperationException(); } @Override public long getSize() { throw new UnsupportedOperationException(); } @Override public boolean isDirectory() { throw new UnsupportedOperationException(); } @Override public boolean isFile() { throw new UnsupportedOperationException(); } @Override public boolean isSpecialFile() { throw new UnsupportedOperationException(); } @Override public boolean isSymbolicLink() { throw new UnsupportedOperationException(); } } /** * A {@link CacheLoader} for a cache of stat calls. * Input: (path, following_symlinks) * Output: FileStatus */ private static CacheLoader<Pair<Path, Symlinks>, Pair<FileStatus, IOException>> newStatLoader() { return new CacheLoader<Pair<Path, Symlinks>, Pair<FileStatus, IOException>>() { @Override public Pair<FileStatus, IOException> load(Pair<Path, Symlinks> p) { try { FileStatus f = p.first.statIfFound(p.second); return Pair.of((f == null) ? NO_STATUS : f, null); } catch (IOException e) { return Pair.of(null, e); } } }; } /** * A {@link CacheLoader} for a cache of readdir calls. * Input: (path, following_symlinks) * Output: A union of (Dirents, IOException). */ private static CacheLoader<Pair<Path, Symlinks>, Pair<Collection<Dirent>, IOException>> newReaddirLoader() { return new CacheLoader<Pair<Path, Symlinks>, Pair<Collection<Dirent>, IOException>>() { @Override public Pair<Collection<Dirent>, IOException> load(Pair<Path, Symlinks> p) { try { // TODO(bazel-team): Consider storing the Collection of Dirent values more compactly // by reusing DirectoryEntryListingStateValue#CompactSortedDirents. return Pair.of(p.first.readdir(p.second), null); } catch (IOException e) { return Pair.of(null, e); } } }; } }