Remove {OutputStore,ActionExecutionValue}.additionalOutputData.
Instead of having another map, when additional data is needed, replace artifactData with it.
There was one place where we exploited the difference between the two: for cases where we get a digest from remote execution but one is not available for the file system, FilesystemValueChecker used only one map.
I fixed this by adding a method to FileArtifactValue that tells whether given two instances, Blaze should assume that there was no change and replicated the existing behavior as faithfully as possible.
RELNOTES: None.
PiperOrigin-RevId: 261086332
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java b/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
index d7cbc28..5ab0b65 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
@@ -56,13 +56,6 @@
@Immutable
@ThreadSafe
public abstract class FileArtifactValue implements SkyValue, HasDigest {
-
- /**
- * Used as as placeholder in {@code OutputStore.artifactData} for artifacts that have entries in
- * {@code OutputStore.additionalOutputData}.
- */
- @AutoCodec public static final FileArtifactValue PLACEHOLDER = new PlaceholderFileValue();
-
/**
* The type of the underlying file system object. If it is a regular file, then it is guaranteed
* to have a digest. Otherwise it does not have a digest.
@@ -71,8 +64,8 @@
/**
* Returns a digest of the content of the underlying file system object; must always return a
- * non-null value for instances of type {@link FileStateType#REGULAR_FILE}. Otherwise may return
- * null.
+ * non-null value for instances of type {@link FileStateType#REGULAR_FILE} that are owned by an
+ * {@code ActionExecutionValue}.
*
* <p>All instances of this interface must either have a digest or return a last-modified time.
* Clients should prefer using the digest for content identification (e.g., for caching), and only
@@ -93,6 +86,9 @@
*/
public abstract long getModifiedTime();
+ // TODO(lberki): This is only used by FileArtifactValue itself. It seems possible to remove this.
+ public abstract FileContentsProxy getContentsProxy();
+
@Nullable
@Override
public BigInteger getValueFingerprint() {
@@ -120,6 +116,11 @@
return this instanceof Singleton;
}
+ /** Returns {@code true} if the file only exists remotely. */
+ public boolean isRemote() {
+ return false;
+ }
+
/**
* Provides a best-effort determination whether the file was changed since the digest was
* computed. This method performs file system I/O, so may be expensive. It's primarily intended to
@@ -127,11 +128,28 @@
* that the file was modified since the digest was computed. Better not upload if we are not sure
* that the cache entry is reliable.
*/
+ // TODO(lberki): This is very similar to couldBeModifiedSince(). Check if we can unify these.
public abstract boolean wasModifiedSinceDigest(Path path) throws IOException;
- /** Returns {@code true} if the file only exists remotely. */
- public boolean isRemote() {
- return false;
+ /**
+ * Returns whether the two {@link FileArtifactValue} instances could be considered the same for
+ * purposes of action invalidation.
+ */
+ // TODO(lberki): This is very similar to wasModifiedSinceDigest(). Check if we can unify these.
+ public boolean couldBeModifiedSince(FileArtifactValue lastKnown) {
+ if (this instanceof Singleton || lastKnown instanceof Singleton) {
+ return true;
+ }
+
+ if (getType() != lastKnown.getType()) {
+ return true;
+ }
+
+ if (getDigest() != null && lastKnown.getDigest() != null) {
+ return !Arrays.equals(getDigest(), lastKnown.getDigest()) || getSize() != lastKnown.getSize();
+ } else {
+ return getModifiedTime() != lastKnown.getModifiedTime();
+ }
}
@Override
@@ -169,8 +187,6 @@
}
}
- public abstract FileContentsProxy getContentsProxy();
-
/**
* Marker interface for singleton implementations of this class.
*
@@ -384,7 +400,7 @@
}
@Override
- public boolean wasModifiedSinceDigest(Path path) throws IOException {
+ public boolean wasModifiedSinceDigest(Path path) {
// TODO(ulfjack): Ideally, we'd attempt to detect intra-build modifications here. I'm
// consciously deferring work here as this code will most likely change again, and we're
// already doing better than before by detecting inter-build modifications.
@@ -397,6 +413,16 @@
}
@Override
+ public boolean couldBeModifiedSince(FileArtifactValue o) {
+ if (!(o instanceof HashedDirectoryArtifactValue)) {
+ return true;
+ }
+
+ HashedDirectoryArtifactValue lastKnown = (HashedDirectoryArtifactValue) o;
+ return !Arrays.equals(digest, lastKnown.digest);
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) {
return true;
@@ -471,6 +497,24 @@
}
@Override
+ public boolean couldBeModifiedSince(FileArtifactValue o) {
+ if (!(o instanceof RegularFileArtifactValue)) {
+ return true;
+ }
+
+ RegularFileArtifactValue lastKnown = (RegularFileArtifactValue) o;
+ if (size != lastKnown.size || dataIsShareable() != lastKnown.dataIsShareable()) {
+ return true;
+ }
+
+ if (digest != null && lastKnown.digest != null) {
+ return !Arrays.equals(digest, lastKnown.digest);
+ } else {
+ return !Objects.equals(proxy, lastKnown.proxy);
+ }
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) {
return true;
@@ -718,7 +762,7 @@
}
@Override
- public boolean wasModifiedSinceDigest(Path path) throws IOException {
+ public boolean wasModifiedSinceDigest(Path path) {
return false;
}
@@ -770,61 +814,4 @@
return "OMITTED_FILE_MARKER";
}
}
-
- private static final class PlaceholderFileValue extends FileArtifactValue implements Singleton {
- private static final BigInteger FINGERPRINT =
- new BigIntegerFingerprint().addString("PlaceholderFileValue").getFingerprint();
-
- private PlaceholderFileValue() {}
-
- @Override
- public long getSize() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public byte[] getDigest() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public FileContentsProxy getContentsProxy() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public BigInteger getValueFingerprint() {
- return FINGERPRINT;
- }
-
- @Override
- public String toString() {
- return "PlaceholderFileValue:Singleton";
- }
-
- @Override
- public FileStateType getType() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getModifiedTime() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean wasModifiedSinceDigest(Path path) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isRemote() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isMarkerValue() {
- throw new UnsupportedOperationException();
- }
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
index ffc04336..4de2fbb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
@@ -19,12 +19,13 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.OwnerlessArtifactWrapper;
+import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.ArtifactOwner;
import com.google.devtools.build.lib.actions.FileArtifactValue;
+import com.google.devtools.build.lib.actions.FileStateType;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -43,37 +44,17 @@
/**
* A value representing an executed action.
- *
- * <p>Concerning the data in this class:
- *
- * <p>We want to track all output data from an ActionExecutionValue. See {@link OutputStore} for a
- * discussion of how its fields {@link OutputStore#artifactData} and {@link
- * OutputStore#additionalOutputData} relate. The relationship is the same between {@link
- * #artifactData} and {@link #additionalOutputData} here.
*/
@Immutable
@ThreadSafe
public class ActionExecutionValue implements SkyValue {
- /**
- * The metadata of all files for this ActionExecutionValue whose filesystem metadata differs from
- * the metadata in {@link #additionalOutputData} or is not present there. This metadata can be
- * checked quickly against the actual filesystem on incremental builds.
- *
- * <p>If additional metadata is not needed here over what is in {@link #additionalOutputData}, the
- * value will be {@link FileArtifactValue#PLACEHOLDER}.
- */
+ /** A map from each output artifact of this action to their {@link FileArtifactValue}s. */
private final ImmutableMap<Artifact, FileArtifactValue> artifactData;
/** The TreeArtifactValue of all TreeArtifacts output by this Action. */
private final ImmutableMap<Artifact, TreeArtifactValue> treeArtifactData;
- /**
- * Contains all remaining data that weren't in the above maps. See {@link
- * OutputStore#getAllAdditionalOutputData}.
- */
- private final ImmutableMap<Artifact, FileArtifactValue> additionalOutputData;
-
@Nullable private final ImmutableList<FilesetOutputSymlink> outputSymlinks;
@Nullable private final NestedSet<Artifact> discoveredModules;
@@ -86,21 +67,38 @@
/**
* @param artifactData Map from Artifacts to corresponding {@link FileArtifactValue}.
* @param treeArtifactData All tree artifact data.
- * @param additionalOutputData Map from Artifacts to values if the FileArtifactValue for this
- * artifact cannot be derived from the corresponding FileValue (see {@link
- * OutputStore#getAllAdditionalOutputData} for when this is necessary). These output data are
- * not used by the {@link FilesystemValueChecker} to invalidate ActionExecutionValues.
* @param outputSymlinks This represents the SymlinkTree which is the output of a fileset action.
* @param discoveredModules cpp modules discovered
*/
private ActionExecutionValue(
Map<Artifact, FileArtifactValue> artifactData,
Map<Artifact, TreeArtifactValue> treeArtifactData,
- Map<Artifact, FileArtifactValue> additionalOutputData,
@Nullable ImmutableList<FilesetOutputSymlink> outputSymlinks,
@Nullable NestedSet<Artifact> discoveredModules) {
+ for (Map.Entry<Artifact, FileArtifactValue> entry : artifactData.entrySet()) {
+ if (entry.getValue().getType() == FileStateType.REGULAR_FILE) {
+ Preconditions.checkArgument(
+ entry.getValue().getDigest() != null, "missing digest for %s", entry.getKey());
+ }
+ }
+
+ for (Map.Entry<Artifact, TreeArtifactValue> tree : treeArtifactData.entrySet()) {
+ for (Map.Entry<TreeFileArtifact, FileArtifactValue> file :
+ tree.getValue().getChildValues().entrySet()) {
+ Preconditions.checkArgument(
+ file.getValue().getType() == FileStateType.REGULAR_FILE,
+ "file %s in tree artifact %s is not a regular file",
+ file.getKey(),
+ tree.getKey());
+ Preconditions.checkArgument(
+ file.getValue().getDigest() != null,
+ "missing digest for file %s in tree artifact %s",
+ file.getKey(),
+ tree.getKey());
+ }
+ }
+
this.artifactData = ImmutableMap.copyOf(artifactData);
- this.additionalOutputData = ImmutableMap.copyOf(additionalOutputData);
this.treeArtifactData = ImmutableMap.copyOf(treeArtifactData);
this.outputSymlinks = outputSymlinks;
this.discoveredModules = discoveredModules;
@@ -114,7 +112,6 @@
return create(
outputStore.getAllArtifactData(),
outputStore.getAllTreeArtifactData(),
- outputStore.getAllAdditionalOutputData(),
outputSymlinks,
discoveredModules,
actionDependsOnBuildId);
@@ -123,38 +120,22 @@
static ActionExecutionValue create(
Map<Artifact, FileArtifactValue> artifactData,
Map<Artifact, TreeArtifactValue> treeArtifactData,
- Map<Artifact, FileArtifactValue> additionalOutputData,
@Nullable ImmutableList<FilesetOutputSymlink> outputSymlinks,
@Nullable NestedSet<Artifact> discoveredModules,
boolean actionDependsOnBuildId) {
return actionDependsOnBuildId
? new CrossServerUnshareableActionExecutionValue(
- artifactData, treeArtifactData, additionalOutputData, outputSymlinks, discoveredModules)
+ artifactData, treeArtifactData, outputSymlinks, discoveredModules)
: new ActionExecutionValue(
- artifactData,
- treeArtifactData,
- additionalOutputData,
- outputSymlinks,
- discoveredModules);
- }
-
- /**
- * Returns metadata for a given artifact, if that metadata cannot be inferred from the
- * corresponding {@link #getData} call for that Artifact. See {@link
- * OutputStore#getAllAdditionalOutputData} for when that can happen.
- */
- @Nullable
- public FileArtifactValue getArtifactValue(Artifact artifact) {
- return additionalOutputData.get(artifact);
+ artifactData, treeArtifactData, outputSymlinks, discoveredModules);
}
/**
* @return The data for each non-middleman output of this action, in the form of the {@link
* FileValue} that would be created for the file if it were to be read from disk.
*/
- FileArtifactValue getData(Artifact artifact) {
- Preconditions.checkState(!additionalOutputData.containsKey(artifact),
- "Should not be requesting data for already-constructed FileArtifactValue: %s", artifact);
+ @Nullable
+ public FileArtifactValue getArtifactValue(Artifact artifact) {
return artifactData.get(artifact);
}
@@ -165,11 +146,11 @@
/**
* @return The map from {@link Artifact}s to the corresponding {@link FileValue}s that would be
- * returned by {@link #getData}. Primarily needed by {@link FilesystemValueChecker}, also
- * called by {@link ArtifactFunction} when aggregating a {@link TreeArtifactValue}.
+ * returned by {@link #getArtifactValue}. Primarily needed by {@link FilesystemValueChecker},
+ * also called by {@link ArtifactFunction} when aggregating a {@link TreeArtifactValue}.
*/
Map<Artifact, FileArtifactValue> getAllFileValues() {
- return Maps.transformEntries(artifactData, this::transformIfPlaceholder);
+ return artifactData;
}
/**
@@ -207,12 +188,6 @@
fp.addPath(entry.getKey().getExecPath());
fp.addBigIntegerOrdered(entry.getValue().getValueFingerprint());
});
- sortMapByArtifactExecPathAndStream(additionalOutputData)
- .forEach(
- (entry) -> {
- fp.addPath(entry.getKey().getExecPath());
- fp.addBigIntegerOrdered(entry.getValue().getValueFingerprint());
- });
if (outputSymlinks != null) {
for (FilesetOutputSymlink symlink : outputSymlinks) {
fp.addBigIntegerOrdered(symlink.getFingerprint());
@@ -234,7 +209,6 @@
return MoreObjects.toStringHelper(this)
.add("artifactData", artifactData)
.add("treeArtifactData", treeArtifactData)
- .add("additionalOutputData", additionalOutputData)
.toString();
}
@@ -252,31 +226,12 @@
ActionExecutionValue o = (ActionExecutionValue) obj;
return artifactData.equals(o.artifactData)
&& treeArtifactData.equals(o.treeArtifactData)
- && additionalOutputData.equals(o.additionalOutputData)
&& (outputSymlinks == null || outputSymlinks.equals(o.outputSymlinks));
}
@Override
public int hashCode() {
- return Objects.hashCode(artifactData, treeArtifactData, additionalOutputData);
- }
-
- /** Transforms PLACEHOLDER values into normal instances. */
- private FileArtifactValue transformIfPlaceholder(Artifact artifact, FileArtifactValue value) {
- Preconditions.checkState(!artifact.isTreeArtifact());
- Preconditions.checkState(!artifact.isMiddlemanArtifact());
-
- if (value != FileArtifactValue.PLACEHOLDER) {
- return value;
- }
-
- FileArtifactValue metadata =
- Preconditions.checkNotNull(
- additionalOutputData.get(artifact),
- "Placeholder without corresponding FileArtifactValue for: %s",
- artifact);
-
- return metadata;
+ return Objects.hashCode(artifactData, treeArtifactData);
}
/**
@@ -288,11 +243,9 @@
CrossServerUnshareableActionExecutionValue(
Map<Artifact, FileArtifactValue> artifactData,
Map<Artifact, TreeArtifactValue> treeArtifactData,
- Map<Artifact, FileArtifactValue> additionalOutputData,
@Nullable ImmutableList<FilesetOutputSymlink> outputSymlinks,
@Nullable NestedSet<Artifact> discoveredModules) {
- super(
- artifactData, treeArtifactData, additionalOutputData, outputSymlinks, discoveredModules);
+ super(artifactData, treeArtifactData, outputSymlinks, discoveredModules);
}
@Override
@@ -366,7 +319,6 @@
return create(
transformKeys(artifactData, newArtifactMap),
transformKeys(treeArtifactData, newArtifactMap),
- transformKeys(additionalOutputData, newArtifactMap),
outputSymlinks,
discoveredModules,
this instanceof CrossServerUnshareableActionExecutionValue);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
index 97ce7c6..f04d2e7 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
@@ -71,10 +71,7 @@
* run, to gather information about the outputs. Second, it is accessed by {@link ArtifactFunction}s
* in order to construct {@link FileArtifactValue}s, and by this class itself to generate {@link
* TreeArtifactValue}s. Third, the {@link FilesystemValueChecker} uses it to determine the set of
- * output files to check for inter-build modifications. Because all these use cases are slightly
- * different, we must occasionally store two versions of the data for a value. See {@link
- * OutputStore#getAllAdditionalOutputData} for elaboration on the difference between these cases,
- * and see the javadoc for the various internal maps to see what is stored where.
+ * output files to check for inter-build modifications.
*/
@VisibleForTesting
public final class ActionMetadataHandler implements MetadataHandler {
@@ -188,12 +185,12 @@
} else if (artifact.isMiddlemanArtifact()) {
// A middleman artifact's data was either already injected from the action cache checker using
// #setDigestForVirtualArtifact, or it has the default middleman value.
- value = store.getAdditionalOutputData(artifact);
+ value = store.getArtifactData(artifact);
if (value != null) {
return metadataFromValue(value);
}
value = FileArtifactValue.DEFAULT_MIDDLEMAN;
- store.putAdditionalOutputData(artifact, value);
+ store.putArtifactData(artifact, value);
return metadataFromValue(value);
} else if (artifact.isTreeArtifact()) {
TreeArtifactValue setValue = getTreeArtifactValue((SpecialArtifact) artifact);
@@ -220,19 +217,7 @@
// results are then stored in Skyframe (and the action cache).
FileArtifactValue fileMetadata = store.getArtifactData(artifact);
if (fileMetadata != null) {
- // Non-middleman artifacts should only have additionalOutputData if they have
- // outputArtifactData. We don't assert this because of concurrency possibilities, but at least
- // we don't check additionalOutputData unless we expect that we might see the artifact there.
- value = store.getAdditionalOutputData(artifact);
- // If additional output data is present for this artifact, we use it in preference to the
- // usual calculation.
- if (value != null) {
- return metadataFromValue(value);
- }
- if (fileMetadata.getType() == FileStateType.NONEXISTENT) {
- throw new FileNotFoundException(artifact.prettyPrint() + " does not exist");
- }
- return fileMetadata;
+ return metadataFromValue(fileMetadata);
}
// No existing metadata; this can happen if the output metadata is not injected after a spawn
@@ -256,10 +241,6 @@
return inputArtifactData.getInput(execPath);
}
- /**
- * See {@link OutputStore#getAllAdditionalOutputData} for why we sometimes need to store
- * additional data, even for normal (non-middleman) artifacts.
- */
private FileArtifactValue maybeStoreAdditionalData(
Artifact artifact, FileArtifactValue data, @Nullable byte[] injectedDigest)
throws IOException {
@@ -303,7 +284,7 @@
data, injectedDigest, !artifact.isConstantMetadata());
}
- store.putAdditionalOutputData(artifact, value);
+ store.putArtifactData(artifact, value);
return metadataFromValue(value);
}
@@ -311,7 +292,7 @@
public void setDigestForVirtualArtifact(Artifact artifact, Md5Digest md5Digest) {
Preconditions.checkArgument(artifact.isMiddlemanArtifact(), artifact);
Preconditions.checkNotNull(md5Digest, artifact);
- store.putAdditionalOutputData(
+ store.putArtifactData(
artifact, FileArtifactValue.createProxy(md5Digest.getDigestBytesUnsafe()));
}
@@ -380,32 +361,30 @@
Maps.newHashMapWithExpectedSize(contents.size());
for (TreeFileArtifact treeFileArtifact : contents) {
- FileArtifactValue cachedValue = store.getAdditionalOutputData(treeFileArtifact);
- if (cachedValue == null) {
- FileArtifactValue fileMetadata = store.getArtifactData(treeFileArtifact);
- // This is similar to what's present in getRealMetadataForArtifact, except
- // we get back the ArtifactFileMetadata, not the metadata.
- // We do not cache exceptions besides nonexistence here, because it is unlikely that the
- // file will be requested from this cache too many times.
- if (fileMetadata == null) {
- try {
- fileMetadata = constructFileArtifactValue(treeFileArtifact, /*statNoFollow=*/ null);
- } catch (FileNotFoundException e) {
- String errorMessage = String.format(
- "Failed to resolve relative path %s inside TreeArtifact %s. "
- + "The associated file is either missing or is an invalid symlink.",
- treeFileArtifact.getParentRelativePath(),
- treeFileArtifact.getParent().getExecPathString());
- throw new IOException(errorMessage, e);
- }
+ FileArtifactValue fileMetadata = store.getArtifactData(treeFileArtifact);
+ // This is similar to what's present in getRealMetadataForArtifact, except
+ // we get back the ArtifactFileMetadata, not the metadata.
+ // We do not cache exceptions besides nonexistence here, because it is unlikely that the
+ // file will be requested from this cache too many times.
+ if (fileMetadata == null) {
+ try {
+ fileMetadata = constructFileArtifactValue(treeFileArtifact, /*statNoFollow=*/ null);
+ } catch (FileNotFoundException e) {
+ String errorMessage =
+ String.format(
+ "Failed to resolve relative path %s inside TreeArtifact %s. "
+ + "The associated file is either missing or is an invalid symlink.",
+ treeFileArtifact.getParentRelativePath(),
+ treeFileArtifact.getParent().getExecPathString());
+ throw new IOException(errorMessage, e);
}
// A minor hack: maybeStoreAdditionalData will force the data to be stored via
// store.putAdditionalOutputData, if the underlying OutputStore supports it.
- cachedValue = maybeStoreAdditionalData(treeFileArtifact, fileMetadata, null);
+ fileMetadata = maybeStoreAdditionalData(treeFileArtifact, fileMetadata, null);
}
- values.put(treeFileArtifact, cachedValue);
+ values.put(treeFileArtifact, fileMetadata);
}
return TreeArtifactValue.create(values);
@@ -537,7 +516,7 @@
if (output instanceof Artifact) {
Artifact artifact = (Artifact) output;
Preconditions.checkState(omittedOutputs.add(artifact), artifact);
- store.putAdditionalOutputData(artifact, FileArtifactValue.OMITTED_FILE_MARKER);
+ store.putArtifactData(artifact, FileArtifactValue.OMITTED_FILE_MARKER);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
index 0e32d6f..b927f63 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
@@ -295,7 +295,8 @@
return value;
}
FileArtifactValue data =
- Preconditions.checkNotNull(actionValue.getData(artifact), "%s %s", artifact, actionValue);
+ Preconditions.checkNotNull(
+ actionValue.getArtifactValue(artifact), "%s %s", artifact, actionValue);
Preconditions.checkNotNull(
data.getDigest(), "Digest should already have been calculated for %s (%s)", artifact, data);
// Directories are special-cased because their mtimes are used, so should have been constructed
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
index 40bf9fd..e94cbd0 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
@@ -308,7 +308,7 @@
try {
FileArtifactValue newData =
ActionMetadataHandler.fileArtifactValueFromArtifact(artifact, stat, tsgm);
- if (!newData.equals(lastKnownData)) {
+ if (newData.couldBeModifiedSince(lastKnownData)) {
updateIntraBuildModifiedCounter(stat != null ? stat.getLastChangeTime() : -1);
modifiedOutputFilesCounter.getAndIncrement();
dirtyKeys.add(key);
@@ -406,7 +406,7 @@
for (Map.Entry<Artifact, FileArtifactValue> entry : actionValue.getAllFileValues().entrySet()) {
Artifact file = entry.getKey();
FileArtifactValue lastKnownData = entry.getValue();
- if (shouldCheckFile(knownModifiedOutputFiles, file)) {
+ if (!file.isMiddlemanArtifact() && shouldCheckFile(knownModifiedOutputFiles, file)) {
try {
FileArtifactValue fileMetadata =
ActionMetadataHandler.fileArtifactValueFromArtifact(file, null, tsgm);
@@ -414,7 +414,7 @@
boolean lastSeenRemotely = (fileValue != null) && fileValue.isRemote();
boolean trustRemoteValue =
fileMetadata.getType() == FileStateType.NONEXISTENT && lastSeenRemotely;
- if (!trustRemoteValue && !fileMetadata.equals(lastKnownData)) {
+ if (!trustRemoteValue && fileMetadata.couldBeModifiedSince(lastKnownData)) {
updateIntraBuildModifiedCounter(
fileMetadata.getType() != FileStateType.NONEXISTENT
? file.getPath().getLastModifiedTime(Symlinks.FOLLOW)
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/MinimalOutputStore.java b/src/main/java/com/google/devtools/build/lib/skyframe/MinimalOutputStore.java
index 0c9b9ca..d12823b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/MinimalOutputStore.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/MinimalOutputStore.java
@@ -25,17 +25,13 @@
* be worthwhile.
*/
final class MinimalOutputStore extends OutputStore {
-
@Override
- void putAdditionalOutputData(Artifact artifact, FileArtifactValue value) {
+ void putArtifactData(Artifact artifact, FileArtifactValue value) {
if (value.isMarkerValue()) {
- super.putAdditionalOutputData(artifact, value);
+ super.putArtifactData(artifact, value);
}
}
@Override
- void putArtifactData(Artifact artifact, FileArtifactValue value) {}
-
- @Override
void addTreeArtifactContents(Artifact artifact, TreeFileArtifact contents) {}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java b/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
index ed034bd..bc75258 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
@@ -28,12 +28,12 @@
/**
* Storage layer for data associated with outputs of an action.
*
- * <p>Data is mainly stored in four maps, {@link #artifactData}, {@link #treeArtifactData}, {@link
- * #additionalOutputData}, and {@link #treeArtifactContents}, all of which are keyed on an {@link
- * Artifact}. For each of these maps, this class exposes standard methods such as {@code get},
- * {@code put}, {@code add}, and {@code getAll}.
+ * <p>Data is mainly stored in three maps, {@link #artifactData}, {@link #treeArtifactData} and
+ * {@link #treeArtifactContents}, all of which are keyed on an {@link Artifact}. For each of these
+ * maps, this class exposes standard methods such as {@code get}, {@code put}, {@code add}, and
+ * {@code getAll}.
*
- * <p>This implementation aggresively stores all data. Subclasses may override mutating methods to
+ * <p>This implementation aggressively stores all data. Subclasses may override mutating methods to
* avoid storing unnecessary data.
*/
@ThreadSafe
@@ -44,9 +44,6 @@
private final ConcurrentMap<Artifact, TreeArtifactValue> treeArtifactData =
new ConcurrentHashMap<>();
- private final ConcurrentMap<Artifact, FileArtifactValue> additionalOutputData =
- new ConcurrentHashMap<>();
-
private final ConcurrentMap<Artifact, Set<TreeFileArtifact>> treeArtifactContents =
new ConcurrentHashMap<>();
@@ -61,12 +58,6 @@
artifactData.put(artifact, value);
}
- /**
- * Returns data for output files that was computed during execution.
- *
- * <p>The value is {@link FileArtifactValue#PLACEHOLDER} if the artifact's metadata is not fully
- * captured in {@link #additionalOutputData}.
- */
final ImmutableMap<Artifact, FileArtifactValue> getAllArtifactData() {
return ImmutableMap.copyOf(artifactData);
}
@@ -88,51 +79,6 @@
return ImmutableMap.copyOf(treeArtifactData);
}
- @Nullable
- final FileArtifactValue getAdditionalOutputData(Artifact artifact) {
- return additionalOutputData.get(artifact);
- }
-
- void putAdditionalOutputData(Artifact artifact, FileArtifactValue value) {
- additionalOutputData.put(artifact, value);
- }
-
- /**
- * Returns data for any output files whose metadata was not computable from the corresponding
- * entry in {@link #artifactData}.
- *
- * <p>There are two bits to consider: the filesystem possessing fast digests and the execution
- * service injecting metadata via {@link ActionMetadataHandler#injectRemoteFile} or {@link
- * ActionMetadataHandler#injectDigest}.
- *
- * <ol>
- * <li>If the filesystem does not possess fast digests, then we will have additional output data
- * for practically every artifact, since we will need to store their digests.
- * <li>If we have a remote execution service injecting metadata, then we will just store that
- * metadata here, and put {@link FileArtifactValue#PLACEHOLDER} objects into {@link
- * #outputArtifactData} if the filesystem supports fast digests, and the actual metadata if
- * the filesystem does not support fast digests.
- * <li>If the filesystem has fast digests <i>but</i> there is no remote execution injecting
- * metadata, then we will not store additional metadata here.
- * </ol>
- *
- * <p>Note that this means that in the vastly common cases (Google-internal, where we have fast
- * digests and remote execution, and Bazel, where there is often neither), this map is always
- * populated. Locally executed actions are the exception to this rule inside Google.
- *
- * <p>Moreover, there are some artifacts that are always stored here. First, middleman artifacts
- * have no corresponding {@link FileArtifactValue}. Second, directories' metadata contain their
- * mtimes, which the {@link FileArtifactValue} does not expose, so that has to be stored
- * separately.
- *
- * <p>Note that for files that need digests, we can't easily inject the digest in the {@link
- * FileArtifactValue} because it would complicate equality-checking on subsequent builds -- if our
- * filesystem doesn't do fast digests, the comparison value would not have a digest.
- */
- final ImmutableMap<Artifact, FileArtifactValue> getAllAdditionalOutputData() {
- return ImmutableMap.copyOf(additionalOutputData);
- }
-
/**
* Returns a set of the given tree artifact's contents.
*
@@ -150,23 +96,13 @@
}
void injectRemoteFile(Artifact output, byte[] digest, long size, int locationIndex) {
- // TODO(shahan): there are a couple of things that could reduce memory usage
- // 1. We might be able to skip creating an entry in `outputArtifactData` and only create
- // the `FileArtifactValue`, but there are likely downstream consumers that expect it that
- // would need to be cleaned up.
- // 2. Instead of creating an `additionalOutputData` entry, we could add the extra
- // `locationIndex` to `FileStateValue`.
injectOutputData(
output, new FileArtifactValue.RemoteFileArtifactValue(digest, size, locationIndex));
}
final void injectOutputData(Artifact output, FileArtifactValue artifactValue) {
injectedFiles.add(output);
-
- // While `artifactValue` carries the important information, consumers also require an entry in
- // `artifactData` so a `PLACEHOLDER` is added to `artifactData`.
- artifactData.put(output, FileArtifactValue.PLACEHOLDER);
- additionalOutputData.put(output, artifactValue);
+ artifactData.put(output, artifactValue);
}
/** Returns a set that tracks which Artifacts have had metadata injected. */
@@ -178,7 +114,6 @@
final void clear() {
artifactData.clear();
treeArtifactData.clear();
- additionalOutputData.clear();
treeArtifactContents.clear();
injectedFiles.clear();
}
@@ -187,7 +122,6 @@
final void remove(Artifact artifact) {
artifactData.remove(artifact);
treeArtifactData.remove(artifact);
- additionalOutputData.remove(artifact);
treeArtifactContents.remove(artifact);
injectedFiles.remove(artifact);
}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
index 9b1e112..8a7183e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTest.java
@@ -261,7 +261,6 @@
public void actionExecutionValueSerialization() throws Exception {
ActionLookupData dummyData = ActionLookupData.create(ALL_OWNER, 0);
Artifact.DerivedArtifact artifact1 = createDerivedArtifact("one");
- Artifact.DerivedArtifact artifact2 = createDerivedArtifact("two");
FileArtifactValue metadata1 =
ActionMetadataHandler.fileArtifactValueFromArtifact(artifact1, null, null);
SpecialArtifact treeArtifact = createDerivedTreeArtifactOnly("tree");
@@ -280,17 +279,15 @@
PathFragment.EMPTY_FRAGMENT, PathFragment.EMPTY_FRAGMENT, PathFragment.EMPTY_FRAGMENT);
ActionExecutionValue actionExecutionValue =
ActionExecutionValue.create(
- ImmutableMap.of(artifact1, metadata1, artifact2, FileArtifactValue.PLACEHOLDER),
+ ImmutableMap.of(artifact1, metadata1, artifact3, FileArtifactValue.DEFAULT_MIDDLEMAN),
ImmutableMap.of(treeArtifact, treeArtifactValue),
- ImmutableMap.of(artifact3, FileArtifactValue.DEFAULT_MIDDLEMAN),
ImmutableList.of(filesetOutputSymlink),
null,
true);
ActionExecutionValue valueWithFingerprint =
ActionExecutionValue.create(
- ImmutableMap.of(artifact1, metadata1, artifact2, FileArtifactValue.PLACEHOLDER),
+ ImmutableMap.of(artifact1, metadata1, artifact3, FileArtifactValue.DEFAULT_MIDDLEMAN),
ImmutableMap.of(treeArtifact, treeArtifactValue),
- ImmutableMap.of(artifact3, FileArtifactValue.DEFAULT_MIDDLEMAN),
ImmutableList.of(filesetOutputSymlink),
null,
true);
@@ -418,7 +415,6 @@
public SkyValue compute(SkyKey skyKey, Environment env) throws InterruptedException {
Map<Artifact, FileArtifactValue> artifactData = new HashMap<>();
Map<Artifact, TreeArtifactValue> treeArtifactData = new HashMap<>();
- Map<Artifact, FileArtifactValue> additionalOutputData = new HashMap<>();
ActionLookupData actionLookupData = (ActionLookupData) skyKey.argument();
ActionLookupValue actionLookupValue =
(ActionLookupValue) env.getValue(actionLookupData.getActionLookupKey());
@@ -440,11 +436,9 @@
} else if (action.getActionType() == MiddlemanType.NORMAL) {
FileArtifactValue fileValue =
ActionMetadataHandler.fileArtifactValueFromArtifact(output, null, null);
- artifactData.put(output, fileValue);
- additionalOutputData.put(
- output, FileArtifactValue.injectDigestForTesting(output, fileValue));
+ artifactData.put(output, FileArtifactValue.injectDigestForTesting(output, fileValue));
} else {
- additionalOutputData.put(output, FileArtifactValue.DEFAULT_MIDDLEMAN);
+ artifactData.put(output, FileArtifactValue.DEFAULT_MIDDLEMAN);
}
} catch (IOException e) {
throw new IllegalStateException(e);
@@ -452,7 +446,6 @@
return ActionExecutionValue.create(
artifactData,
treeArtifactData,
- additionalOutputData,
/*outputSymlinks=*/ null,
/*discoveredModules=*/ null,
/*actionDependsOnBuildId=*/ false);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
index 4d9e29a..cb06db2 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
@@ -383,13 +383,13 @@
actionKey1,
actionValue(
new TestAction(
- Runnables.doNothing(), ImmutableSet.<Artifact>of(), ImmutableSet.of(out1)),
- forceDigests),
+ Runnables.doNothing(), ImmutableSet.<Artifact>of(), ImmutableSet.of(out1))),
actionKey2,
actionValue(
new TestAction(
- Runnables.doNothing(), ImmutableSet.<Artifact>of(), ImmutableSet.of(out2)),
- forceDigests)));
+ Runnables.doNothing(),
+ ImmutableSet.<Artifact>of(),
+ ImmutableSet.of(out2)))));
EvaluationContext evaluationContext =
EvaluationContext.newBuilder()
.setKeepGoing(false)
@@ -727,15 +727,20 @@
// TODO(bazel-team): Add some tests for FileSystemValueChecker#changedKeys*() methods.
// Presently these appear to be untested.
- private ActionExecutionValue actionValue(Action action, boolean forceDigest) {
+ private ActionExecutionValue actionValue(Action action) {
Map<Artifact, FileArtifactValue> artifactData = new HashMap<>();
for (Artifact output : action.getOutputs()) {
try {
Path path = output.getPath();
- FileStatusWithDigest stat =
- forceDigest ? statWithDigest(path, path.statIfFound(Symlinks.NOFOLLOW)) : null;
- artifactData.put(
- output, ActionMetadataHandler.fileArtifactValueFromArtifact(output, stat, null));
+ FileArtifactValue noDigest =
+ ActionMetadataHandler.fileArtifactValueFromArtifact(
+ output,
+ FileStatusWithDigestAdapter.adapt(path.statIfFound(Symlinks.NOFOLLOW)),
+ null);
+ FileArtifactValue withDigest =
+ FileArtifactValue.createFromInjectedDigest(
+ noDigest, path.getDigest(), !output.isConstantMetadata());
+ artifactData.put(output, withDigest);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -743,7 +748,6 @@
return ActionExecutionValue.create(
artifactData,
ImmutableMap.<Artifact, TreeArtifactValue>of(),
- ImmutableMap.<Artifact, FileArtifactValue>of(),
/*outputSymlinks=*/ null,
/*discoveredModules=*/ null,
/*actionDependsOnBuildId=*/ false);
@@ -756,7 +760,6 @@
return ActionExecutionValue.create(
ImmutableMap.of(),
ImmutableMap.of(emptyDir, emptyValue),
- ImmutableMap.<Artifact, FileArtifactValue>of(),
/*outputSymlinks=*/ null,
/*discoveredModules=*/ null,
/*actionDependsOnBuildId=*/ false);
@@ -774,10 +777,17 @@
dirDatum = new HashMap<>();
directoryData.put(output.getParent(), dirDatum);
}
- FileArtifactValue fileValue =
- ActionMetadataHandler.fileArtifactValueFromArtifact(output, null, null);
- dirDatum.put(output, FileArtifactValue.injectDigestForTesting(output, fileValue));
- fileData.put(output, fileValue);
+ Path path = output.getPath();
+ FileArtifactValue noDigest =
+ ActionMetadataHandler.fileArtifactValueFromArtifact(
+ output,
+ FileStatusWithDigestAdapter.adapt(path.statIfFound(Symlinks.NOFOLLOW)),
+ null);
+ FileArtifactValue withDigest =
+ FileArtifactValue.createFromInjectedDigest(
+ noDigest, path.getDigest(), !output.isConstantMetadata());
+ dirDatum.put(output, withDigest);
+ fileData.put(output, withDigest);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -792,7 +802,6 @@
return ActionExecutionValue.create(
fileData,
treeArtifactData,
- ImmutableMap.<Artifact, FileArtifactValue>of(),
/*outputSymlinks=*/ null,
/*discoveredModules=*/ null,
/*actionDependsOnBuildId=*/ false);
@@ -810,9 +819,8 @@
}
TreeArtifactValue treeArtifactValue = TreeArtifactValue.create(childFileValues.build());
return ActionExecutionValue.create(
- Collections.emptyMap(),
- Collections.singletonMap(output, treeArtifactValue),
ImmutableMap.of(),
+ Collections.singletonMap(output, treeArtifactValue),
/* outputSymlinks= */ null,
/* discoveredModules= */ null,
/* actionDependsOnBuildId= */ false);
@@ -821,9 +829,8 @@
private ActionExecutionValue actionValueWithRemoteArtifact(
Artifact output, RemoteFileArtifactValue value) {
return ActionExecutionValue.create(
- Collections.singletonMap(output, FileArtifactValue.PLACEHOLDER),
- ImmutableMap.of(),
Collections.singletonMap(output, value),
+ ImmutableMap.of(),
/* outputSymlinks= */ null,
/* discoveredModules= */ null,
/* actionDependsOnBuildId= */ false);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
index 76407e4..0a03985 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TreeArtifactMetadataTest.java
@@ -41,9 +41,11 @@
import com.google.devtools.build.lib.actions.util.TestAction.DummyAction;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.vfs.FileStatus;
+import com.google.devtools.build.lib.vfs.FileStatusWithDigestAdapter;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Symlinks;
import com.google.devtools.build.skyframe.EvaluationContext;
import com.google.devtools.build.skyframe.EvaluationResult;
import com.google.devtools.build.skyframe.MemoizingEvaluator;
@@ -288,11 +290,17 @@
for (PathFragment subpath : testTreeArtifactContents) {
try {
TreeFileArtifact suboutput = ActionInputHelper.treeFileArtifact(output, subpath);
- FileArtifactValue fileValue =
- ActionMetadataHandler.fileArtifactValueFromArtifact(suboutput, null, null);
- fileData.put(suboutput, fileValue);
- treeArtifactData.put(
- suboutput, FileArtifactValue.injectDigestForTesting(suboutput, fileValue));
+ Path path = suboutput.getPath();
+ FileArtifactValue noDigest =
+ ActionMetadataHandler.fileArtifactValueFromArtifact(
+ suboutput,
+ FileStatusWithDigestAdapter.adapt(path.statIfFound(Symlinks.NOFOLLOW)),
+ null);
+ FileArtifactValue withDigest =
+ FileArtifactValue.createFromInjectedDigest(
+ noDigest, path.getDigest(), !output.isConstantMetadata());
+ fileData.put(suboutput, withDigest);
+ treeArtifactData.put(suboutput, withDigest);
} catch (IOException e) {
throw new SkyFunctionException(e, Transience.TRANSIENT) {};
}
@@ -303,7 +311,6 @@
return ActionExecutionValue.create(
fileData,
ImmutableMap.of(output, treeArtifactValue),
- ImmutableMap.<Artifact, FileArtifactValue>of(),
/*outputSymlinks=*/ null,
/*discoveredModules=*/ null,
/*actionDependsOnBuildId=*/ false);