Make deterministic the error that is stored in TransitiveTraversalValue. When there are multiple errors, we don't want non-determinism.
PiperOrigin-RevId: 240208756
diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
index 3d56f5b..5184e8e 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
@@ -892,9 +892,9 @@
for (Map.Entry<SkyKey, SkyValue> successfulEntry : successfulEntries) {
successfulKeysBuilder.add(successfulEntry.getKey());
TransitiveTraversalValue value = (TransitiveTraversalValue) successfulEntry.getValue();
- String firstErrorMessage = value.getFirstErrorMessage();
- if (firstErrorMessage != null) {
- errorMessagesBuilder.add(firstErrorMessage);
+ String errorMessage = value.getErrorMessage();
+ if (errorMessage != null) {
+ errorMessagesBuilder.add(errorMessage);
}
}
ImmutableSet<SkyKey> successfulKeys = successfulKeysBuilder.build();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java
index 3f72cf9..b7d8b43 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunction.java
@@ -21,13 +21,13 @@
import com.google.devtools.build.lib.packages.NoSuchPackageException;
import com.google.devtools.build.lib.packages.NoSuchTargetException;
import com.google.devtools.build.lib.packages.NoSuchThingException;
-import com.google.devtools.build.lib.skyframe.TransitiveTraversalFunction.FirstErrorMessageAccumulator;
import com.google.devtools.build.lib.util.GroupedList;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.ValueOrException2;
import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@@ -35,12 +35,13 @@
/**
* This class is like {@link TransitiveTargetFunction}, but the values it returns do not contain
* {@link NestedSet}s. It performs the side-effects of {@link TransitiveTargetFunction} (i.e.,
- * ensuring that transitive targets and their packages have been loaded). It evaluates to a
- * {@link TransitiveTraversalValue} that contains the first error message it encountered, and a
- * set of names of providers if the target is a rule.
+ * ensuring that transitive targets and their packages have been loaded). It evaluates to a {@link
+ * TransitiveTraversalValue} that contains the first error message it encountered, and a set of
+ * names of providers if the target is a rule.
*/
public class TransitiveTraversalFunction
- extends TransitiveBaseTraversalFunction<FirstErrorMessageAccumulator> {
+ extends TransitiveBaseTraversalFunction<
+ TransitiveTraversalFunction.DeterministicErrorMessageAccumulator> {
@Override
Label argumentFromKey(SkyKey key) {
@@ -53,15 +54,16 @@
}
@Override
- FirstErrorMessageAccumulator processTarget(Label label, TargetAndErrorIfAny targetAndErrorIfAny) {
+ DeterministicErrorMessageAccumulator processTarget(
+ Label label, TargetAndErrorIfAny targetAndErrorIfAny) {
NoSuchTargetException errorIfAny = targetAndErrorIfAny.getErrorLoadingTarget();
String errorMessageIfAny = errorIfAny == null ? null : errorIfAny.getMessage();
- return new FirstErrorMessageAccumulator(errorMessageIfAny);
+ return DeterministicErrorMessageAccumulator.create(errorMessageIfAny);
}
@Override
void processDeps(
- FirstErrorMessageAccumulator accumulator,
+ DeterministicErrorMessageAccumulator accumulator,
EventHandler eventHandler,
TargetAndErrorIfAny targetAndErrorIfAny,
Iterable<Map.Entry<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>>
@@ -78,9 +80,9 @@
accumulator.maybeSet(e.getMessage());
continue;
}
- String firstErrorMessage = transitiveTraversalValue.getFirstErrorMessage();
- if (firstErrorMessage != null) {
- accumulator.maybeSet(firstErrorMessage);
+ String errorMessage = transitiveTraversalValue.getErrorMessage();
+ if (errorMessage != null) {
+ accumulator.maybeSet(errorMessage);
}
}
}
@@ -104,14 +106,14 @@
}
@Override
- SkyValue computeSkyValue(TargetAndErrorIfAny targetAndErrorIfAny,
- FirstErrorMessageAccumulator accumulator) {
+ SkyValue computeSkyValue(
+ TargetAndErrorIfAny targetAndErrorIfAny, DeterministicErrorMessageAccumulator accumulator) {
boolean targetLoadedSuccessfully = targetAndErrorIfAny.getErrorLoadingTarget() == null;
- String firstErrorMessage = accumulator.getFirstErrorMessage();
+ String errorMessage = accumulator.getErrorMessage();
return targetLoadedSuccessfully
- ? TransitiveTraversalValue.forTarget(targetAndErrorIfAny.getTarget(), firstErrorMessage)
+ ? TransitiveTraversalValue.forTarget(targetAndErrorIfAny.getTarget(), errorMessage)
: TransitiveTraversalValue.unsuccessfulTransitiveTraversal(
- firstErrorMessage, targetAndErrorIfAny.getTarget());
+ errorMessage, targetAndErrorIfAny.getTarget());
}
@Override
@@ -178,28 +180,47 @@
}
/**
- * Keeps track of the first error message encountered while traversing itself and its
- * dependencies.
+ * Keeps track of a deterministic error message encountered while traversing itself and its
+ * dependencies: either the error it was initialized with, or the shortest error it encounters,
+ * with ties broken alphabetically.
+ *
+ * <p>This preserves the behavior that the local target's error is the most important, and is
+ * cheap (constant-time) to compute the comparison between strings, unless they have the same
+ * length, which is unlikely.
*/
- static class FirstErrorMessageAccumulator {
-
- @Nullable private String firstErrorMessage;
-
- public FirstErrorMessageAccumulator(@Nullable String firstErrorMessage) {
- this.firstErrorMessage = firstErrorMessage;
- }
-
- /** Remembers {@param errorMessage} if it is the first error message. */
- void maybeSet(String errorMessage) {
- Preconditions.checkNotNull(errorMessage);
- if (firstErrorMessage == null) {
- firstErrorMessage = errorMessage;
- }
- }
-
+ interface DeterministicErrorMessageAccumulator {
@Nullable
- String getFirstErrorMessage() {
- return firstErrorMessage;
+ String getErrorMessage();
+
+ default void maybeSet(String errorMessage) {}
+
+ static DeterministicErrorMessageAccumulator create(@Nullable String errorMessage) {
+ if (errorMessage != null) {
+ return () -> errorMessage;
+ }
+ return new UpdateableErrorMessageAccumulator();
+ }
+
+ class UpdateableErrorMessageAccumulator implements DeterministicErrorMessageAccumulator {
+ private static final Comparator<String> LENGTH_THEN_ALPHABETICAL =
+ Comparator.nullsLast(
+ Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
+
+ @Nullable private String errorMessage;
+
+ @Override
+ public void maybeSet(String errorMessage) {
+ Preconditions.checkNotNull(errorMessage);
+ if (LENGTH_THEN_ALPHABETICAL.compare(this.errorMessage, errorMessage) > 0) {
+ this.errorMessage = errorMessage;
+ }
+ }
+
+ @Nullable
+ @Override
+ public String getErrorMessage() {
+ return errorMessage;
+ }
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalValue.java
index 655cadd..be9c828 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalValue.java
@@ -62,20 +62,18 @@
}
static TransitiveTraversalValue unsuccessfulTransitiveTraversal(
- String firstErrorMessage, Target target) {
+ String errorMessage, Target target) {
return new TransitiveTraversalValueWithError(
- Preconditions.checkNotNull(firstErrorMessage), target.getTargetKind());
+ Preconditions.checkNotNull(errorMessage), target.getTargetKind());
}
- static TransitiveTraversalValue forTarget(Target target, @Nullable String firstErrorMessage) {
- if (firstErrorMessage == null) {
+ static TransitiveTraversalValue forTarget(Target target, @Nullable String errorMessage) {
+ if (errorMessage == null) {
if (target instanceof Rule && ((Rule) target).getRuleClassObject().isSkylark()) {
Rule rule = (Rule) target;
// Do not intern values for skylark rules.
return TransitiveTraversalValue.create(
- rule.getRuleClassObject().getAdvertisedProviders(),
- rule.getTargetKind(),
- firstErrorMessage);
+ rule.getRuleClassObject().getAdvertisedProviders(), rule.getTargetKind(), errorMessage);
} else {
TransitiveTraversalValue value = VALUES_BY_TARGET_KIND.get(target.getTargetKind());
if (value != null) {
@@ -95,18 +93,18 @@
return value;
}
} else {
- return new TransitiveTraversalValueWithError(firstErrorMessage, target.getTargetKind());
+ return new TransitiveTraversalValueWithError(errorMessage, target.getTargetKind());
}
}
@AutoCodec.Instantiator
public static TransitiveTraversalValue create(
- AdvertisedProviderSet providers, String kind, @Nullable String firstErrorMessage) {
+ AdvertisedProviderSet providers, String kind, @Nullable String errorMessage) {
TransitiveTraversalValue value =
- firstErrorMessage == null
+ errorMessage == null
? new TransitiveTraversalValueWithoutError(providers, kind)
- : new TransitiveTraversalValueWithError(firstErrorMessage, kind);
- if (firstErrorMessage == null) {
+ : new TransitiveTraversalValueWithError(errorMessage, kind);
+ if (errorMessage == null) {
TransitiveTraversalValue oldValue = VALUE_INTERNER.getCanonical(value);
return oldValue == null ? value : oldValue;
}
@@ -128,11 +126,11 @@
}
/**
- * Returns the first error message, if any, from loading the target and its transitive
+ * Returns a deterministic error message, if any, from loading the target and its transitive
* dependencies.
*/
@Nullable
- public abstract String getFirstErrorMessage();
+ public abstract String getErrorMessage();
@Override
public boolean equals(Object o) {
@@ -143,14 +141,14 @@
return false;
}
TransitiveTraversalValue that = (TransitiveTraversalValue) o;
- return Objects.equals(this.getFirstErrorMessage(), that.getFirstErrorMessage())
+ return Objects.equals(this.getErrorMessage(), that.getErrorMessage())
&& Objects.equals(this.getKind(), that.getKind())
&& this.getProviders().equals(that.getProviders());
}
@Override
public int hashCode() {
- return Objects.hash(getFirstErrorMessage(), getKind(), getProviders());
+ return Objects.hash(getErrorMessage(), getKind(), getProviders());
}
@ThreadSafe
@@ -181,7 +179,7 @@
@Override
@Nullable
- public String getFirstErrorMessage() {
+ public String getErrorMessage() {
return null;
}
@@ -196,12 +194,11 @@
/** A transitive target reference with error. */
public static final class TransitiveTraversalValueWithError extends TransitiveTraversalValue {
- private final String firstErrorMessage;
+ private final String errorMessage;
- private TransitiveTraversalValueWithError(String firstErrorMessage, String kind) {
+ private TransitiveTraversalValueWithError(String errorMessage, String kind) {
super(kind);
- this.firstErrorMessage =
- StringCanonicalizer.intern(Preconditions.checkNotNull(firstErrorMessage));
+ this.errorMessage = StringCanonicalizer.intern(Preconditions.checkNotNull(errorMessage));
}
@Override
@@ -216,14 +213,14 @@
@Override
@Nullable
- public String getFirstErrorMessage() {
- return firstErrorMessage;
+ public String getErrorMessage() {
+ return errorMessage;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
- .add("error", firstErrorMessage)
+ .add("error", errorMessage)
.add("kind", getKind())
.toString();
}
diff --git a/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java b/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java
index 7c8bdc3..4351c56 100644
--- a/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java
+++ b/src/main/java/com/google/devtools/build/skyframe/ValueOrException2.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.skyframe;
+import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nullable;
/** Wrapper for a value or the typed exception thrown when trying to compute it. */
@@ -23,7 +24,8 @@
@Nullable
public abstract SkyValue get() throws E1, E2;
- static <E1 extends Exception, E2 extends Exception>
+ @VisibleForTesting
+ public static <E1 extends Exception, E2 extends Exception>
ValueOrException2<E1, E2> fromUntypedException(
ValueOrUntypedException voe, Class<E1> exceptionClass1, Class<E2> exceptionClass2) {
SkyValue value = voe.getValue();
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunctionTest.java
index 0fa3e05..9ca9b5d 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TransitiveTraversalFunctionTest.java
@@ -31,6 +31,8 @@
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.devtools.build.skyframe.SkyFunction;
import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.ValueOrException2;
+import com.google.devtools.build.skyframe.ValueOrUntypedException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -98,6 +100,101 @@
assertThat(wasOptimizationUsed.get()).isTrue();
}
+ @Test
+ public void multipleErrorsForTransitiveTraversalFunction() throws Exception {
+ Label label = Label.parseAbsolute("//foo:foo", ImmutableMap.of());
+ Package pkg =
+ scratchPackage(
+ "workspace",
+ label.getPackageIdentifier(),
+ "sh_library(name = '" + label.getName() + "', deps = [':bar', ':baz'])");
+ TargetAndErrorIfAnyImpl targetAndErrorIfAny =
+ new TargetAndErrorIfAnyImpl(
+ /*packageLoadedSuccessfully=*/ true,
+ /*errorLoadingTarget=*/ null,
+ pkg.getTarget(label.getName()));
+ TransitiveTraversalFunction function =
+ new TransitiveTraversalFunction() {
+ @Override
+ LoadTargetResults loadTarget(Environment env, Label label) {
+ return targetAndErrorIfAny;
+ }
+ };
+ SkyKey dep1 = function.getKey(Label.parseAbsolute("//foo:bar", ImmutableMap.of()));
+ SkyKey dep2 = function.getKey(Label.parseAbsolute("//foo:baz", ImmutableMap.of()));
+ ImmutableMap<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>
+ returnedDeps =
+ ImmutableMap.of(dep1, makeException("bad bar"), dep2, makeException("bad baz"));
+ SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
+ // Try two evaluations, with the environment reversing the order of the map it returns.
+ when(mockEnv.getValuesOrThrow(
+ Mockito.any(),
+ Mockito.eq(NoSuchPackageException.class),
+ Mockito.eq(NoSuchTargetException.class)))
+ .thenReturn(returnedDeps);
+ when(mockEnv.valuesMissing()).thenReturn(false);
+
+ assertThat(
+ ((TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv))
+ .getErrorMessage())
+ .isEqualTo("bad bar");
+ ImmutableMap<SkyKey, ValueOrException2<NoSuchPackageException, NoSuchTargetException>>
+ reversedDeps =
+ ImmutableMap.of(dep2, makeException("bad baz"), dep1, makeException("bad bar"));
+ when(mockEnv.getValuesOrThrow(
+ Mockito.any(),
+ Mockito.eq(NoSuchPackageException.class),
+ Mockito.eq(NoSuchTargetException.class)))
+ .thenReturn(reversedDeps);
+ assertThat(
+ ((TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv))
+ .getErrorMessage())
+ .isEqualTo("bad bar");
+ }
+
+ @Test
+ public void selfErrorWins() throws Exception {
+ Label label = Label.parseAbsolute("//foo:foo", ImmutableMap.of());
+ Package pkg =
+ scratchPackage(
+ "workspace",
+ label.getPackageIdentifier(),
+ "sh_library(name = '" + label.getName() + "', deps = [':bar', ':baz'])");
+ TargetAndErrorIfAnyImpl targetAndErrorIfAny =
+ new TargetAndErrorIfAnyImpl(
+ /*packageLoadedSuccessfully=*/ true,
+ /*errorLoadingTarget=*/ new NoSuchTargetException("self error is long and last"),
+ pkg.getTarget(label.getName()));
+ TransitiveTraversalFunction function =
+ new TransitiveTraversalFunction() {
+ @Override
+ LoadTargetResults loadTarget(Environment env, Label label) {
+ return targetAndErrorIfAny;
+ }
+ };
+ SkyKey dep = function.getKey(Label.parseAbsolute("//foo:bar", ImmutableMap.of()));
+ SkyFunction.Environment mockEnv = Mockito.mock(SkyFunction.Environment.class);
+ when(mockEnv.getValuesOrThrow(
+ Mockito.any(),
+ Mockito.eq(NoSuchPackageException.class),
+ Mockito.eq(NoSuchTargetException.class)))
+ .thenReturn(ImmutableMap.of(dep, makeException("bad bar")));
+ when(mockEnv.valuesMissing()).thenReturn(false);
+
+ assertThat(
+ ((TransitiveTraversalValue) function.compute(function.getKey(label), mockEnv))
+ .getErrorMessage())
+ .isEqualTo("self error is long and last");
+ }
+
+ private static ValueOrException2<NoSuchPackageException, NoSuchTargetException> makeException(
+ String errorMessage) {
+ ValueOrUntypedException exn =
+ ValueOrUntypedException.ofExn(new NoSuchTargetException(errorMessage));
+ return ValueOrException2.fromUntypedException(
+ exn, NoSuchPackageException.class, NoSuchTargetException.class);
+ }
+
private Package scratchPackage(String workspaceName, PackageIdentifier packageId, String... lines)
throws Exception {
Path buildFile = scratch.file("" + packageId.getSourceRoot() + "/BUILD", lines);