blob: f1a8385a637feca7c1b3436abecd818d19e07081 [file] [log] [blame]
// 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.skyframe;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
import java.util.Collection;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* Information about why a {@link SkyValue} failed to evaluate successfully.
*
* <p>This is intended only for use in alternative {@code MemoizingEvaluator} implementations.
*/
public final class ErrorInfo {
/** Create an ErrorInfo from a {@link ReifiedSkyFunctionException}. */
public static ErrorInfo fromException(
ReifiedSkyFunctionException skyFunctionException, boolean isTransitivelyTransient) {
Exception rootCauseException = skyFunctionException.getCause();
return new ErrorInfo(
Preconditions.checkNotNull(rootCauseException, "Cause is null"),
/*cycles=*/ ImmutableList.of(),
skyFunctionException.isTransient(),
isTransitivelyTransient || skyFunctionException.isTransient(),
skyFunctionException.isCatastrophic());
}
/** Create an ErrorInfo from a {@link CycleInfo}. */
public static ErrorInfo fromCycle(CycleInfo cycleInfo) {
return new ErrorInfo(
/*exception=*/ null,
ImmutableList.of(cycleInfo),
/*isDirectlyTransient=*/ false,
/*isTransitivelyTransient=*/ false,
/*isCatastrophic=*/ false);
}
/** Create an ErrorInfo from a collection of existing errors. */
public static ErrorInfo fromChildErrors(SkyKey currentValue, Collection<ErrorInfo> childErrors) {
Preconditions.checkNotNull(currentValue, "currentValue must not be null");
Preconditions.checkState(
!childErrors.isEmpty(), "childErrors may not be empty %s", currentValue);
ImmutableList.Builder<CycleInfo> cycleBuilder = ImmutableList.builder();
Exception firstException = null;
boolean isTransitivelyTransient = false;
boolean isCatastrophic = false;
for (ErrorInfo child : childErrors) {
if (firstException == null) {
// Arbitrarily pick the first error.
firstException = child.exception;
}
cycleBuilder.addAll(CycleInfo.prepareCycles(currentValue, child.cycles));
isTransitivelyTransient |= child.isTransitivelyTransient;
isCatastrophic |= child.isCatastrophic;
}
return new ErrorInfo(
firstException,
cycleBuilder.build(),
/*isDirectlyTransient=*/ false,
isTransitivelyTransient,
isCatastrophic);
}
@Nullable private final Exception exception;
private final ImmutableList<CycleInfo> cycles;
private final boolean isDirectlyTransient;
private final boolean isTransitivelyTransient;
private final boolean isCatastrophic;
@SuppressWarnings("LenientFormatStringValidation")
public ErrorInfo(
@Nullable Exception exception,
ImmutableList<CycleInfo> cycles,
boolean isDirectlyTransient,
boolean isTransitivelyTransient,
boolean isCatastrophic) {
this.exception = exception;
this.cycles = cycles;
this.isDirectlyTransient = isDirectlyTransient;
this.isTransitivelyTransient = isTransitivelyTransient;
this.isCatastrophic = isCatastrophic;
// Expected 0 args, but got 1.
Preconditions.checkArgument(
exception != null || !cycles.isEmpty(),
"At least one of exception and cycles must be present",
this);
// Expected 0 args, but got 1.
Preconditions.checkArgument(
!isDirectlyTransient || isTransitivelyTransient,
"Cannot be directly transient but not transitively transient",
this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ErrorInfo)) {
return false;
}
ErrorInfo other = (ErrorInfo) obj;
if (!Objects.equals(cycles, other.cycles)) {
return false;
}
// Don't check the specific exception as most exceptions don't implement equality but at least
// check their types and messages are the same.
if (exception != other.exception) {
if (exception == null || other.exception == null) {
return false;
}
// Class objects are singletons with a single class loader.
if (exception.getClass() != other.exception.getClass()) {
return false;
}
if (!Objects.equals(exception.getMessage(), other.exception.getMessage())) {
return false;
}
}
return isDirectlyTransient == other.isDirectlyTransient
&& isTransitivelyTransient == other.isTransitivelyTransient
&& isCatastrophic == other.isCatastrophic;
}
@Override
public int hashCode() {
return Objects.hash(
exception == null ? null : exception.getClass(),
exception == null ? "" : exception.getMessage(),
cycles,
isDirectlyTransient,
isTransitivelyTransient,
isCatastrophic);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("exception", exception)
.add("cycles", cycles)
.add("isCatastrophic", isCatastrophic)
.add("isDirectlyTransient", isDirectlyTransient)
.add("isTransitivelyTransient", isTransitivelyTransient)
.toString();
}
/**
* The exception thrown when building a value. May be null if value's only error is depending
* on a cycle.
*
* <p>The exception is used for reporting and thus may ultimately be rethrown by the caller.
* As well, during a --nokeep_going evaluation, if an error value is encountered from an earlier
* --keep_going build, the exception to be thrown is taken from here.
*/
@Nullable public Exception getException() {
return exception;
}
/**
* Any cycles found when building this value.
*
* <p>If there are a large number of cycles, only a limited number are returned here.
*
* <p>If this value has a child through which there are multiple paths to the same cycle, only one
* path is returned here. However, if there are multiple paths to the same cycle, each of which
* goes through a different child, each of them is returned here.
*/
public ImmutableList<CycleInfo> getCycleInfo() {
return cycles;
}
/**
* Returns true iff the error is directly transient, i.e. if there was a transient error
* encountered during the computation itself.
*
* <p>A return of {@code true} implies that {@link #isTransitivelyTransient()} is also {@code
* true}.
*/
public boolean isDirectlyTransient() {
return isDirectlyTransient;
}
/**
* Returns true iff the error is transitively transient, i.e. if retrying the same computation
* could lead to a different result.
*/
public boolean isTransitivelyTransient() {
return isTransitivelyTransient;
}
/**
* Returns true iff the error is catastrophic, i.e. it should halt even for a keepGoing update()
* call.
*/
public boolean isCatastrophic() {
return isCatastrophic;
}
}