Allow NodeEntries to prune their deps based on fingerprinting the dep group. This is useful in situations in which the deps themselves are forbidden from value-based pruning because of subtle version bugs.
PiperOrigin-RevId: 224922819
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 71ba6bc..5dd418b 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
@@ -26,9 +26,11 @@
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.BigIntegerFingerprintUtils;
import com.google.devtools.build.skyframe.SkyValue;
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;
import javax.annotation.Nullable;
@@ -101,6 +103,17 @@
*/
public abstract long getModifiedTime();
+ @Nullable
+ @Override
+ public BigInteger getValueFingerprint() {
+ byte[] digest = getDigest();
+ if (digest != null) {
+ return BigIntegerFingerprintUtils.fingerprintOf(digest);
+ }
+ // TODO(janakr): return fingerprint in other cases: symlink, directory.
+ return null;
+ }
+
/**
* Index used to resolve remote files.
*
diff --git a/src/main/java/com/google/devtools/build/skyframe/AbstractExceptionalParallelEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/AbstractExceptionalParallelEvaluator.java
index c8c3aa6..56fe045 100644
--- a/src/main/java/com/google/devtools/build/skyframe/AbstractExceptionalParallelEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/AbstractExceptionalParallelEvaluator.java
@@ -683,7 +683,10 @@
prevEntry.noDepsLastBuild(), "existing entry for %s has deps: %s", key, prevEntry);
prevEntry.markRebuilding();
}
- prevEntry.setValue(value, version);
+ prevEntry.setValue(
+ value,
+ version,
+ prevEntry.canPruneDepsByFingerprint() ? new DepFingerprintList.Builder(0).build() : null);
// Now that this key's injected value is set, it is no longer dirty.
progressReceiver.injected(key);
}
diff --git a/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
index 2c758fd..51731f5 100644
--- a/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
@@ -38,6 +38,7 @@
import com.google.devtools.build.skyframe.SkyFunctionEnvironment.UndonePreviouslyRequestedDep;
import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
import com.google.devtools.build.skyframe.ThinNodeEntry.DirtyType;
+import java.math.BigInteger;
import java.time.Duration;
import java.util.Collection;
import java.util.Iterator;
@@ -316,7 +317,21 @@
skyKey, reverseDeps, state.getVersion(), EnqueueParentBehavior.ENQUEUE);
return DirtyOutcome.ALREADY_PROCESSED;
case NEEDS_REBUILDING:
- maybeMarkRebuilding(state);
+ if (state.canPruneDepsByFingerprint()) {
+ Iterable<SkyKey> lastDirectDepsKeys =
+ state.getLastDirectDepsGroupWhenPruningDepsByFingerprint();
+ if (lastDirectDepsKeys != null) {
+ BigInteger groupFingerprint =
+ composeDepFingerprints(
+ lastDirectDepsKeys,
+ evaluatorContext.getBatchValues(
+ skyKey, Reason.DEP_REQUESTED, lastDirectDepsKeys));
+ if (state.unmarkNeedsRebuildingIfGroupUnchangedUsingFingerprint(groupFingerprint)) {
+ return maybeHandleDirtyNode(state);
+ }
+ }
+ }
+ state.markRebuilding();
return DirtyOutcome.NEEDS_EVALUATION;
case NEEDS_FORCED_REBUILDING:
state.forceRebuild();
@@ -855,6 +870,31 @@
return true;
}
+ static BigInteger composeDepFingerprints(
+ Iterable<SkyKey> directDepGroup, Map<SkyKey, ? extends NodeEntry> depEntries)
+ throws InterruptedException {
+ BigInteger groupFingerprint = BigInteger.ZERO;
+ for (SkyKey dep : directDepGroup) {
+ NodeEntry depEntry = depEntries.get(dep);
+ if (!isDoneForBuild(depEntry)) {
+ // Something weird happened: maybe something fell out of graph or was restarted?
+ return null;
+ }
+ SkyValue depValue = depEntry.getValue();
+ if (depValue == null) {
+ return null;
+ }
+ BigInteger depFingerprint = depValue.getValueFingerprint();
+ if (depFingerprint == null) {
+ // TODO(janakr): Use transitive data here.
+ return null;
+ }
+ groupFingerprint =
+ BigIntegerFingerprintUtils.composeOrdered(groupFingerprint, depFingerprint);
+ }
+ return groupFingerprint;
+ }
+
/**
* Return true if the entry does not need to be re-evaluated this build. The entry will need to be
* re-evaluated if it is not done, but also if it was not completely evaluated last build and this
diff --git a/src/main/java/com/google/devtools/build/skyframe/BUILD b/src/main/java/com/google/devtools/build/skyframe/BUILD
index e8cd3ac..7de5e27 100644
--- a/src/main/java/com/google/devtools/build/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/skyframe/BUILD
@@ -19,6 +19,7 @@
visibility = ["//visibility:public"],
deps = [
"//third_party:guava",
+ "//third_party:jsr305",
],
)
diff --git a/src/main/java/com/google/devtools/build/skyframe/BigIntegerFingerprintUtils.java b/src/main/java/com/google/devtools/build/skyframe/BigIntegerFingerprintUtils.java
new file mode 100644
index 0000000..337a454
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skyframe/BigIntegerFingerprintUtils.java
@@ -0,0 +1,92 @@
+// Copyright 2018 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.skyframe;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+
+/** Utility class for fingerprint composition. */
+public final class BigIntegerFingerprintUtils {
+ private static final int BITS = 128;
+ public static final int BYTES = BITS / 8;
+
+ private static final BigInteger UINT128_LIMIT = BigInteger.ONE.shiftLeft(BITS);
+ private static final BigInteger RELATIVE_PRIME = BigInteger.valueOf(31);
+
+ private BigIntegerFingerprintUtils() {}
+
+ public static BigInteger compose(BigInteger v1, BigInteger v2) {
+ BigInteger temp = v1.add(v2);
+ if (temp.compareTo(UINT128_LIMIT) >= 0) {
+ return temp.subtract(UINT128_LIMIT);
+ }
+ return temp;
+ }
+
+ /**
+ * Converts a byte array to a BigInteger in the fingerprint range (no more than {@link
+ * #UINT128_LIMIT}).
+ */
+ public static BigInteger fingerprintOf(byte[] bytes) {
+ int numSegments = bytes.length / BYTES;
+ if (numSegments == 0) {
+ return new BigInteger(bytes);
+ }
+ BigInteger result = new BigInteger(/*signum=*/ 1, Arrays.copyOf(bytes, BYTES));
+ for (int segment = 1; segment < numSegments; segment++) {
+ result =
+ composeOrdered(
+ result,
+ new BigInteger(
+ /*signum=*/ 1,
+ Arrays.copyOfRange(bytes, segment * BYTES, (segment + 1) * BYTES)));
+ }
+ if (numSegments * BYTES < bytes.length) {
+ result =
+ composeOrdered(
+ result,
+ new BigInteger(
+ /*signum=*/ 1, Arrays.copyOfRange(bytes, numSegments * BYTES, bytes.length)));
+ }
+ return result;
+ }
+ /**
+ * Unordered, nullable composition.
+ *
+ * <p>null is absorbing and is used to convey errors and unavailability
+ */
+ @Nullable
+ public static BigInteger composeNullable(@Nullable BigInteger v1, @Nullable BigInteger v2) {
+ if (v1 == null || v2 == null) {
+ return null;
+ }
+ return compose(v1, v2);
+ }
+
+ public static BigInteger composeOrdered(BigInteger accumulator, BigInteger v) {
+ return compose(accumulator.multiply(RELATIVE_PRIME).mod(UINT128_LIMIT), v);
+ }
+
+ @Nullable
+ public static BigInteger composeOrderedNullable(
+ @Nullable BigInteger accumulator, @Nullable BigInteger v) {
+ if (accumulator == null || v == null) {
+ return null;
+ }
+ return compose(accumulator.multiply(RELATIVE_PRIME).mod(UINT128_LIMIT), v);
+ }
+
+}
diff --git a/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java
index 147f681..d6c1b5e 100644
--- a/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/DelegatingNodeEntry.java
@@ -15,6 +15,7 @@
import com.google.devtools.build.lib.util.GroupedList;
import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper;
+import java.math.BigInteger;
import java.util.Collection;
import java.util.Set;
import javax.annotation.Nullable;
@@ -54,8 +55,10 @@
}
@Override
- public Set<SkyKey> setValue(SkyValue value, Version version) throws InterruptedException {
- return getDelegate().setValue(value, version);
+ public Set<SkyKey> setValue(
+ SkyValue value, Version version, DepFingerprintList depFingerprintList)
+ throws InterruptedException {
+ return getDelegate().setValue(value, version, depFingerprintList);
}
@Override
@@ -116,6 +119,24 @@
}
@Override
+ public boolean canPruneDepsByFingerprint() {
+ return getDelegate().canPruneDepsByFingerprint();
+ }
+
+ @Nullable
+ @Override
+ public Iterable<SkyKey> getLastDirectDepsGroupWhenPruningDepsByFingerprint()
+ throws InterruptedException {
+ return getDelegate().getLastDirectDepsGroupWhenPruningDepsByFingerprint();
+ }
+
+ @Override
+ public boolean unmarkNeedsRebuildingIfGroupUnchangedUsingFingerprint(
+ BigInteger groupFingerprint) {
+ return getDelegate().unmarkNeedsRebuildingIfGroupUnchangedUsingFingerprint(groupFingerprint);
+ }
+
+ @Override
public Iterable<SkyKey> getAllReverseDepsForNodeBeingDeleted() {
return getDelegate().getAllReverseDepsForNodeBeingDeleted();
}
diff --git a/src/main/java/com/google/devtools/build/skyframe/DepFingerprintList.java b/src/main/java/com/google/devtools/build/skyframe/DepFingerprintList.java
new file mode 100644
index 0000000..4a7fbf4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skyframe/DepFingerprintList.java
@@ -0,0 +1,96 @@
+// Copyright 2018 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.skyframe;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterators;
+import java.math.BigInteger;
+import java.util.Iterator;
+import javax.annotation.Nullable;
+
+/** List of fingerprints, each of which is a nullable {@link BigInteger}. Ordered and immutable. */
+public class DepFingerprintList implements Iterable<BigInteger> {
+ /**
+ * Marker object indicating that dep fingerprints are not being tracked, and so there should be no
+ * attempts to use this dep fingerprints object. Clients can compare their {@code
+ * DepFingerprintList} object to this one using reference equality to see if they are tracking dep
+ * fingerprints. This object should never be passed in as a valid {@code DepFingerprintList}
+ * object.
+ */
+ public static final DepFingerprintList NOT_TRACKING_MARKER = new NoDepGroupsFingerprintMarker();
+
+ private static final DepFingerprintList EMPTY_DEP_GROUPS_FINGERPRINT =
+ new DepFingerprintList(new BigInteger[0]);
+
+ private final BigInteger[] fingerprints;
+
+ private DepFingerprintList(BigInteger[] fingerprints) {
+ this.fingerprints = fingerprints;
+ }
+
+ /**
+ * Returns the {@link BigInteger} for the dep group at {@code index}. A null value indicates that
+ * a fingerprint could not be computed for this group, so that equality-by-dep-group checking
+ * cannot be performed for this group.
+ */
+ @Nullable
+ public BigInteger get(int index) {
+ return fingerprints[index];
+ }
+
+ @Override
+ public Iterator<BigInteger> iterator() {
+ return Iterators.forArray(fingerprints);
+ }
+
+ private static class NoDepGroupsFingerprintMarker extends DepFingerprintList {
+ private NoDepGroupsFingerprintMarker() {
+ super(null);
+ }
+
+ @Override
+ public BigInteger get(int index) {
+ throw new UnsupportedOperationException("No dep groups fingerprint (" + index + ")");
+ }
+
+ @Override
+ public Iterator<BigInteger> iterator() {
+ throw new UnsupportedOperationException("No dep groups fingerprint");
+ }
+ }
+
+ /** Builder for {@code DepFingerprintList}. */
+ public static class Builder {
+ private int curIndex = 0;
+ private final BigInteger[] fingerprints;
+
+ public Builder(int size) {
+ fingerprints = new BigInteger[size];
+ }
+
+ public void add(BigInteger fingerprint) {
+ fingerprints[curIndex++] = fingerprint;
+ }
+
+ public DepFingerprintList build() {
+ Preconditions.checkState(
+ curIndex == fingerprints.length, "%s %s", curIndex, fingerprints.length);
+ if (fingerprints.length == 0) {
+ return EMPTY_DEP_GROUPS_FINGERPRINT;
+ }
+ return new DepFingerprintList(fingerprints);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/skyframe/DirtyBuildingState.java b/src/main/java/com/google/devtools/build/skyframe/DirtyBuildingState.java
index ea5ec34..eca9fd7 100644
--- a/src/main/java/com/google/devtools/build/skyframe/DirtyBuildingState.java
+++ b/src/main/java/com/google/devtools/build/skyframe/DirtyBuildingState.java
@@ -149,6 +149,15 @@
}
}
+ public final void unmarkNeedsRebuilding() {
+ Preconditions.checkState(dirtyState == DirtyState.NEEDS_REBUILDING, this);
+ if (getNumOfGroupsInLastBuildDirectDeps() == dirtyDirectDepIndex) {
+ dirtyState = DirtyState.VERIFIED_CLEAN;
+ } else {
+ dirtyState = DirtyState.CHECK_DEPENDENCIES;
+ }
+ }
+
/**
* Returns true if {@code newValue}.equals the value from the last time this node was built.
* Should only be used by {@link NodeEntry#setValue}.
@@ -219,6 +228,10 @@
dirtyState = DirtyState.REBUILDING;
}
+ public int getLastDirtyDirectDepIndex() {
+ return dirtyDirectDepIndex - 1;
+ }
+
protected MoreObjects.ToStringHelper getStringHelper() {
return MoreObjects.toStringHelper(this)
.add("dirtyState", dirtyState)
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
index f0dadac..bd453ca3 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryNodeEntry.java
@@ -22,6 +22,7 @@
import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper;
import com.google.devtools.build.skyframe.KeyToConsolidate.Op;
import com.google.devtools.build.skyframe.KeyToConsolidate.OpToStoreBare;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -295,13 +296,21 @@
// although this method itself is synchronized, there are unsynchronized consumers of the version
// and the value.
@Override
- public synchronized Set<SkyKey> setValue(SkyValue value, Version version)
+ public synchronized Set<SkyKey> setValue(
+ SkyValue value, Version version, DepFingerprintList depFingerprintList)
throws InterruptedException {
Preconditions.checkState(isReady(), "%s %s", this, value);
+ if (depFingerprintList != null) {
+ logError(
+ new IllegalStateException(
+ String.format(
+ "Expect no depFingerprintList here: %s %s %s %s",
+ this, depFingerprintList, value, version)));
+ }
assertVersionCompatibleWhenSettingValue(version, value);
this.lastEvaluatedVersion = version;
- if (!isEligibleForChangePruning()) {
+ if (!isEligibleForChangePruningOnUnchangedValue()) {
this.lastChangedVersion = version;
this.value = value;
} else if (isDirty() && getDirtyBuildingState().unchangedFromLastBuild(value)) {
@@ -324,13 +333,13 @@
}
/**
- * Returns {@code true} if this node is eligible to be change-pruned when its value has not
+ * Returns {@code true} if this node is eligible to be change pruned when its value has not
* changed from the last build.
*
- * <p>Implementations need not check whether the value has changed - nodes will only be
- * change-pruned if the value has not changed.
+ * <p>Implementations need not check whether the value has changed - this will only be called if
+ * the value has not changed.
*/
- public boolean isEligibleForChangePruning() {
+ public boolean isEligibleForChangePruningOnUnchangedValue() {
return true;
}
@@ -624,8 +633,36 @@
}
@Override
+ public boolean canPruneDepsByFingerprint() {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public Iterable<SkyKey> getLastDirectDepsGroupWhenPruningDepsByFingerprint()
+ throws InterruptedException {
+ throw new UnsupportedOperationException(this.toString());
+ }
+
+ @Override
+ public boolean unmarkNeedsRebuildingIfGroupUnchangedUsingFingerprint(
+ BigInteger groupFingerprint) {
+ throw new UnsupportedOperationException(this.toString());
+ }
+
+ /**
+ * If this entry {@link #canPruneDepsByFingerprint} and has that data, returns a list of dep group
+ * fingerprints. Otherwise returns null.
+ */
+ @Nullable
+ public DepFingerprintList getDepFingerprintList() {
+ Preconditions.checkState(isDone(), this);
+ return null;
+ }
+
+ @Override
public synchronized void markRebuilding() {
- getDirtyBuildingState().markRebuilding(isEligibleForChangePruning());
+ getDirtyBuildingState().markRebuilding(isEligibleForChangePruningOnUnchangedValue());
}
@SuppressWarnings("unchecked")
@@ -715,8 +752,7 @@
return signaledDeps > NOT_EVALUATING_SENTINEL;
}
- @Override
- public synchronized String toString() {
+ protected synchronized MoreObjects.ToStringHelper toStringHelper() {
return MoreObjects.toStringHelper(this)
.add("identity", System.identityHashCode(this))
.add("value", value)
@@ -725,8 +761,12 @@
.add("directDeps", isDone() ? GroupedList.create(directDeps) : directDeps)
.add("signaledDeps", signaledDeps)
.add("reverseDeps", ReverseDepsUtility.toString(this))
- .add("dirtyBuildingState", dirtyBuildingState)
- .toString();
+ .add("dirtyBuildingState", dirtyBuildingState);
+ }
+
+ @Override
+ public final synchronized String toString() {
+ return toStringHelper().toString();
}
protected synchronized InMemoryNodeEntry cloneNodeEntry(InMemoryNodeEntry newEntry) {
diff --git a/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java b/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java
index 90b36b7..5c5797cb 100644
--- a/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java
+++ b/src/main/java/com/google/devtools/build/skyframe/NodeEntry.java
@@ -16,6 +16,7 @@
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.util.GroupedList;
import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper;
+import java.math.BigInteger;
import java.util.Collection;
import java.util.Set;
import javax.annotation.Nullable;
@@ -177,9 +178,13 @@
* entry determines that the new value is equal to the previous value, the entry will keep its
* current version. Callers can query that version to see if the node considers its value to have
* changed.
+ *
+ * <p>{@code depFingerprintList} must be non-null iff {@link #canPruneDepsByFingerprint}.
*/
@ThreadSafe
- Set<SkyKey> setValue(SkyValue value, Version version) throws InterruptedException;
+ Set<SkyKey> setValue(
+ SkyValue value, Version version, @Nullable DepFingerprintList depFingerprintList)
+ throws InterruptedException;
/**
* Queries if the node is done and adds the given key as a reverse dependency. The return code
@@ -345,6 +350,42 @@
Set<SkyKey> getAllRemainingDirtyDirectDeps() throws InterruptedException;
/**
+ * Whether this entry stores fingerprints of its dep groups, which enables it to change-prune
+ * (avoid re-evaluating) if the values in a dep group haven't changed. This is normally handled by
+ * version-based change pruning, but some graph evaluation modes do not support that (see {@link
+ * InMemoryNodeEntry#isEligibleForChangePruningOnUnchangedValue}). For such evaluation modes, the
+ * downstream dependents of nodes that have not changed can avoid re-evaluation via this
+ * change-pruning mode.
+ */
+ boolean canPruneDepsByFingerprint();
+
+ /**
+ * Can only be called if {@link #canPruneDepsByFingerprint} is true, during dirtiness checking
+ * when the entry is marked as {@link DirtyState#NEEDS_REBUILDING}. Returns the last direct deps
+ * group that was checked, so that its fingerprint can be calculated.
+ *
+ * <p>Returns null if fingerprint information is not stored for this group, and so computing the
+ * new fingerprint would be useless.
+ */
+ @Nullable
+ Iterable<SkyKey> getLastDirectDepsGroupWhenPruningDepsByFingerprint() throws InterruptedException;
+
+ /**
+ * Can only be called if {@link #canPruneDepsByFingerprint} is true, during dirtiness checking
+ * when the entry is marked as {@link DirtyState#NEEDS_REBUILDING}. {@code groupFingerprint} is
+ * the fingerprint that was calculated from the deps returned by {@link
+ * #getLastDirectDepsGroupWhenPruningDepsByFingerprint}.
+ *
+ * <p>If the dep group fingerprint is the same as the stored value, modifies this entry so that
+ * the dirty state is what it would have been if the last dep group had <i>not</i> triggered a
+ * {@link DirtyState#NEEDS_REBUILDING} state: either {@link DirtyState#CHECK_DEPENDENCIES} or
+ * {@link DirtyState#VERIFIED_CLEAN} (if this was the last dep group). Returns true if the
+ * fingerprints matched.
+ */
+ @ThreadSafe
+ boolean unmarkNeedsRebuildingIfGroupUnchangedUsingFingerprint(BigInteger groupFingerprint);
+
+ /**
* Notifies a node that it is about to be rebuilt. This method can only be called if the node
* {@link DirtyState#NEEDS_REBUILDING}. After this call, this node is ready to be rebuilt (it will
* be in {@link DirtyState#REBUILDING}).
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
index fdb7c2d..c475d77 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
@@ -724,6 +724,22 @@
oldDepEntry.removeReverseDep(skyKey);
}
}
+ DepFingerprintList depFingerprintList = null;
+ if (primaryEntry.canPruneDepsByFingerprint()) {
+ DepFingerprintList.Builder depFingerprintListBuilder =
+ new DepFingerprintList.Builder(temporaryDirectDeps.listSize());
+ // TODO(janakr): in the common case, all these nodes may be locally cached. Do multi-level
+ // checking a la #getDepValuesForDoneNodeFromErrorOrDepsOrGraph to save graph lookups?
+ Map<SkyKey, ? extends NodeEntry> allDeps =
+ evaluatorContext.getBatchValues(
+ skyKey, Reason.DEP_REQUESTED, temporaryDirectDeps.getAllElementsAsIterable());
+ for (Collection<SkyKey> depGroup : temporaryDirectDeps) {
+ depFingerprintListBuilder.add(
+ AbstractParallelEvaluator.composeDepFingerprints(depGroup, allDeps));
+ }
+ depFingerprintList = depFingerprintListBuilder.build();
+ }
+
Version evaluationVersion = maxChildVersion;
if (bubbleErrorInfo != null) {
// Cycles can lead to a state where the versions of done children don't accurately reflect the
@@ -746,7 +762,8 @@
Version previousVersion = primaryEntry.getVersion();
// If this entry is dirty, setValue may not actually change it, if it determines that
// the data being written now is the same as the data already present in the entry.
- Set<SkyKey> reverseDeps = primaryEntry.setValue(valueWithMetadata, evaluationVersion);
+ Set<SkyKey> reverseDeps =
+ primaryEntry.setValue(valueWithMetadata, evaluationVersion, depFingerprintList);
// Note that if this update didn't actually change the entry, this version may not be
// evaluationVersion.
Version currentVersion = primaryEntry.getVersion();
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyValue.java b/src/main/java/com/google/devtools/build/skyframe/SkyValue.java
index 4f0e19a..fdac334 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyValue.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyValue.java
@@ -14,9 +14,20 @@
package com.google.devtools.build.skyframe;
import java.io.Serializable;
+import java.math.BigInteger;
+import javax.annotation.Nullable;
/**
* A return value of a {@code SkyFunction}.
*/
public interface SkyValue extends Serializable {
+ /**
+ * If non-null, a fingerprint for this value such that two values are equal iff they have the same
+ * fingerprint. For use during dep-fingerprint-based change pruning: see {@link
+ * NodeEntry#canPruneDepsByFingerprint}.
+ */
+ @Nullable
+ default BigInteger getValueFingerprint() {
+ return null;
+ }
}
diff --git a/src/test/java/com/google/devtools/build/skyframe/DeterministicHelper.java b/src/test/java/com/google/devtools/build/skyframe/DeterministicHelper.java
index d9e125b..9a83bde 100644
--- a/src/test/java/com/google/devtools/build/skyframe/DeterministicHelper.java
+++ b/src/test/java/com/google/devtools/build/skyframe/DeterministicHelper.java
@@ -147,9 +147,11 @@
}
@Override
- public Set<SkyKey> setValue(SkyValue value, Version version) throws InterruptedException {
+ public Set<SkyKey> setValue(
+ SkyValue value, Version version, DepFingerprintList depFingerprintList)
+ throws InterruptedException {
TreeSet<SkyKey> result = new TreeSet<>(ALPHABETICAL_SKYKEY_COMPARATOR);
- result.addAll(super.setValue(value, version));
+ result.addAll(super.setValue(value, version, depFingerprintList));
return result;
}
}
diff --git a/src/test/java/com/google/devtools/build/skyframe/GraphTest.java b/src/test/java/com/google/devtools/build/skyframe/GraphTest.java
index 366385c..868db68 100644
--- a/src/test/java/com/google/devtools/build/skyframe/GraphTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/GraphTest.java
@@ -210,7 +210,7 @@
}
waitForStart.countDown();
waitForAddedRdep.await(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- entry.setValue(new StringValue("foo1"), startingVersion);
+ entry.setValue(new StringValue("foo1"), startingVersion, null);
waitForSetValue.countDown();
wrapper.waitForTasksAndMaybeThrow();
assertThat(ExecutorUtil.interruptibleShutdown(pool)).isFalse();
@@ -226,7 +226,7 @@
sameEntry.markDirty(DirtyType.CHANGE);
startEvaluation(sameEntry);
sameEntry.markRebuilding();
- sameEntry.setValue(new StringValue("foo2"), getNextVersion(startingVersion));
+ sameEntry.setValue(new StringValue("foo2"), getNextVersion(startingVersion), null);
assertThat(graph.get(null, Reason.OTHER, key).getValue()).isEqualTo(new StringValue("foo2"));
if (checkRdeps()) {
assertThat(graph.get(null, Reason.OTHER, key).getReverseDepsForDoneEntry())
@@ -282,7 +282,7 @@
if (startEvaluation(entry).equals(DependencyState.NEEDS_SCHEDULING)) {
assertThat(valuesSet.add(key)).isTrue();
// Set to done.
- entry.setValue(new StringValue("bar" + keyNum), startingVersion);
+ entry.setValue(new StringValue("bar" + keyNum), startingVersion, null);
assertThat(entry.isDone()).isTrue();
}
} catch (InterruptedException e) {
@@ -332,7 +332,7 @@
for (int i = 0; i < numKeys; i++) {
NodeEntry entry = entries.get(key("foo" + i));
startEvaluation(entry);
- entry.setValue(new StringValue("bar"), startingVersion);
+ entry.setValue(new StringValue("bar"), startingVersion, null);
}
assertThat(graph.get(null, Reason.OTHER, key("foo" + 0))).isNotNull();
@@ -377,7 +377,8 @@
addTemporaryDirectDep(entry, key("dep"));
entry.signalDep();
- entry.setValue(new StringValue("bar" + keyNum), getNextVersion(startingVersion));
+ entry.setValue(
+ new StringValue("bar" + keyNum), getNextVersion(startingVersion), null);
} catch (InterruptedException e) {
throw new IllegalStateException(keyNum + ", " + entry, e);
}
diff --git a/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java b/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java
index ef26381..1dc4717 100644
--- a/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java
+++ b/src/test/java/com/google/devtools/build/skyframe/InMemoryNodeEntryTest.java
@@ -613,7 +613,7 @@
NodeEntry entry =
new InMemoryNodeEntry() {
@Override
- public boolean isEligibleForChangePruning() {
+ public boolean isEligibleForChangePruningOnUnchangedValue() {
return false;
}
};
@@ -756,7 +756,7 @@
.isEqualTo(DependencyState.NEEDS_SCHEDULING);
addTemporaryDirectDep(entry, originalChild);
entry.signalDep();
- entry.setValue(originalValue, version);
+ entry.setValue(originalValue, version, null);
entry.addReverseDepAndCheckIfDone(key("parent1"));
InMemoryNodeEntry clone1 = entry.cloneNodeEntry();
entry.addReverseDepAndCheckIfDone(key("parent2"));
@@ -770,7 +770,7 @@
addTemporaryDirectDep(clone2, newChild);
clone2.signalDep();
clone2.markRebuilding();
- clone2.setValue(updatedValue, version.next());
+ clone2.setValue(updatedValue, version.next(), null);
assertThat(entry.getVersion()).isEqualTo(version);
assertThat(clone1.getVersion()).isEqualTo(version);
@@ -813,7 +813,7 @@
entry.signalDep();
}
}
- entry.setValue(new IntegerValue(42), IntVersion.of(42L));
+ entry.setValue(new IntegerValue(42), IntVersion.of(42L), null);
int i = 0;
GroupedList<SkyKey> entryGroupedDirectDeps = entry.getGroupedDirectDeps();
assertThat(Iterables.size(entryGroupedDirectDeps)).isEqualTo(groupedDirectDeps.size());
@@ -827,7 +827,8 @@
throws InterruptedException {
return entry.setValue(
ValueWithMetadata.normal(value, errorInfo, NO_EVENTS, NO_POSTS),
- IntVersion.of(graphVersion));
+ IntVersion.of(graphVersion),
+ null);
}
private static void addTemporaryDirectDep(NodeEntry entry, SkyKey key) {
diff --git a/src/test/java/com/google/devtools/build/skyframe/NotifyingHelper.java b/src/test/java/com/google/devtools/build/skyframe/NotifyingHelper.java
index dee4b9e..302e469 100644
--- a/src/test/java/com/google/devtools/build/skyframe/NotifyingHelper.java
+++ b/src/test/java/com/google/devtools/build/skyframe/NotifyingHelper.java
@@ -254,9 +254,11 @@
}
@Override
- public Set<SkyKey> setValue(SkyValue value, Version version) throws InterruptedException {
+ public Set<SkyKey> setValue(
+ SkyValue value, Version version, DepFingerprintList depFingerprintList)
+ throws InterruptedException {
graphListener.accept(myKey, EventType.SET_VALUE, Order.BEFORE, value);
- Set<SkyKey> result = super.setValue(value, version);
+ Set<SkyKey> result = super.setValue(value, version, depFingerprintList);
graphListener.accept(myKey, EventType.SET_VALUE, Order.AFTER, value);
return result;
}