use of com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException in project bazel by bazelbuild.
the class ParallelEvaluator method bubbleErrorUp.
/**
* Walk up graph to find a top-level node (without parents) that wanted this failure. Store the
* failed nodes along the way in a map, with ErrorInfos that are appropriate for that layer.
* Example:
*
* <pre>
* foo bar
* \ /
* unrequested baz
* \ |
* failed-node
* </pre>
*
* User requests foo, bar. When failed-node fails, we look at its parents. unrequested is not
* in-flight, so we replace failed-node by baz and repeat. We look at baz's parents. foo is
* in-flight, so we replace baz by foo. Since foo is a top-level node and doesn't have parents, we
* then break, since we know a top-level node, foo, that depended on the failed node.
*
* <p>There's the potential for a weird "track jump" here in the case:
*
* <pre>
* foo
* / \
* fail1 fail2
* </pre>
*
* If fail1 and fail2 fail simultaneously, fail2 may start propagating up in the loop below.
* However, foo requests fail1 first, and then throws an exception based on that. This is not
* incorrect, but may be unexpected.
*
* <p>Returns a map of errors that have been constructed during the bubbling up, so that the
* appropriate error can be returned to the caller, even though that error was not written to the
* graph. If a cycle is detected during the bubbling, this method aborts and returns null so that
* the normal cycle detection can handle the cycle.
*
* <p>Note that we are not propagating error to the first top-level node but to the highest one,
* because during this process we can add useful information about error from other nodes.
*/
private Map<SkyKey, ValueWithMetadata> bubbleErrorUp(final ErrorInfo leafFailure, SkyKey errorKey, Iterable<SkyKey> skyKeys) throws InterruptedException {
Set<SkyKey> rootValues = ImmutableSet.copyOf(skyKeys);
ErrorInfo error = leafFailure;
Map<SkyKey, ValueWithMetadata> bubbleErrorInfo = new HashMap<>();
boolean externalInterrupt = false;
while (true) {
NodeEntry errorEntry = Preconditions.checkNotNull(graph.get(null, Reason.ERROR_BUBBLING, errorKey), errorKey);
Iterable<SkyKey> reverseDeps = errorEntry.isDone() ? errorEntry.getReverseDeps() : errorEntry.getInProgressReverseDeps();
// We should break from loop only when node doesn't have any parents.
if (Iterables.isEmpty(reverseDeps)) {
Preconditions.checkState(rootValues.contains(errorKey), "Current key %s has to be a top-level key: %s", errorKey, rootValues);
break;
}
SkyKey parent = null;
NodeEntry parentEntry = null;
for (SkyKey bubbleParent : reverseDeps) {
if (bubbleErrorInfo.containsKey(bubbleParent)) {
// We are in a cycle. Don't try to bubble anything up -- cycle detection will kick in.
return null;
}
NodeEntry bubbleParentEntry = Preconditions.checkNotNull(graph.get(errorKey, Reason.ERROR_BUBBLING, bubbleParent), "parent %s of %s not in graph", bubbleParent, errorKey);
// Might be the parent that requested the error.
if (bubbleParentEntry.isDone()) {
// This parent is cached from a previous evaluate call. We shouldn't bubble up to it
// since any error message produced won't be meaningful to this evaluate call.
// The child error must also be cached from a previous build.
Preconditions.checkState(errorEntry.isDone(), "%s %s", errorEntry, bubbleParentEntry);
Version parentVersion = bubbleParentEntry.getVersion();
Version childVersion = errorEntry.getVersion();
Preconditions.checkState(childVersion.atMost(evaluatorContext.getGraphVersion()) && !childVersion.equals(evaluatorContext.getGraphVersion()), "child entry is not older than the current graph version, but had a done parent. " + "child: %s childEntry: %s, childVersion: %s" + "bubbleParent: %s bubbleParentEntry: %s, parentVersion: %s, graphVersion: %s", errorKey, errorEntry, childVersion, bubbleParent, bubbleParentEntry, parentVersion, evaluatorContext.getGraphVersion());
Preconditions.checkState(parentVersion.atMost(evaluatorContext.getGraphVersion()) && !parentVersion.equals(evaluatorContext.getGraphVersion()), "parent entry is not older than the current graph version. " + "child: %s childEntry: %s, childVersion: %s" + "bubbleParent: %s bubbleParentEntry: %s, parentVersion: %s, graphVersion: %s", errorKey, errorEntry, childVersion, bubbleParent, bubbleParentEntry, parentVersion, evaluatorContext.getGraphVersion());
continue;
}
if (evaluatorContext.getProgressReceiver().isInflight(bubbleParent) && bubbleParentEntry.getTemporaryDirectDeps().expensiveContains(errorKey)) {
// Only bubble up to parent if it's part of this build. If this node was dirtied and
// re-evaluated, but in a build without this parent, we may try to bubble up to that
// parent. Don't -- it's not part of the build.
// Similarly, the parent may not yet have requested this dep in its dirtiness-checking
// process. Don't bubble up to it in that case either.
parent = bubbleParent;
parentEntry = bubbleParentEntry;
break;
}
}
if (parent == null) {
Preconditions.checkState(rootValues.contains(errorKey), "Current key %s has to be a top-level key: %s, %s", errorKey, rootValues, errorEntry);
break;
}
Preconditions.checkNotNull(parentEntry, "%s %s", errorKey, parent);
errorKey = parent;
SkyFunction factory = evaluatorContext.getSkyFunctions().get(parent.functionName());
if (parentEntry.isDirty()) {
switch(parentEntry.getDirtyState()) {
case CHECK_DEPENDENCIES:
// If this value's child was bubbled up to, it did not signal this value, and so we must
// manually make it ready to build.
parentEntry.signalDep();
// Fall through to NEEDS_REBUILDING, since state is now NEEDS_REBUILDING.
case NEEDS_REBUILDING:
maybeMarkRebuilding(parentEntry);
// Fall through to REBUILDING.
case REBUILDING:
break;
default:
throw new AssertionError(parent + " not in valid dirty state: " + parentEntry);
}
}
SkyFunctionEnvironment env = new SkyFunctionEnvironment(parent, new GroupedList<SkyKey>(), bubbleErrorInfo, ImmutableSet.<SkyKey>of(), evaluatorContext);
externalInterrupt = externalInterrupt || Thread.currentThread().isInterrupted();
try {
// This build is only to check if the parent node can give us a better error. We don't
// care about a return value.
factory.compute(parent, env);
} catch (InterruptedException interruptedException) {
// Do nothing.
// This throw happens if the builder requested the failed node, and then checked the
// interrupted state later -- getValueOrThrow sets the interrupted bit after the failed
// value is requested, to prevent the builder from doing too much work.
} catch (SkyFunctionException builderException) {
// Clear interrupted status. We're not listening to interrupts here.
Thread.interrupted();
ReifiedSkyFunctionException reifiedBuilderException = new ReifiedSkyFunctionException(builderException, parent);
if (reifiedBuilderException.getRootCauseSkyKey().equals(parent)) {
error = ErrorInfo.fromException(reifiedBuilderException, /*isTransitivelyTransient=*/
false);
bubbleErrorInfo.put(errorKey, ValueWithMetadata.error(ErrorInfo.fromChildErrors(errorKey, ImmutableSet.of(error)), env.buildEvents(parentEntry, /*missingChildren=*/
true)));
continue;
}
} finally {
// Clear interrupted status. We're not listening to interrupts here.
Thread.interrupted();
}
// Builder didn't throw an exception, so just propagate this one up.
bubbleErrorInfo.put(errorKey, ValueWithMetadata.error(ErrorInfo.fromChildErrors(errorKey, ImmutableSet.of(error)), env.buildEvents(parentEntry, /*missingChildren=*/
true)));
}
// bubbling node calls getValueOrThrow() on a node in error.
if (externalInterrupt) {
Thread.currentThread().interrupt();
}
return bubbleErrorInfo;
}
use of com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException in project bazel by bazelbuild.
the class ErrorInfoTest method testFromChildErrors.
@Test
public void testFromChildErrors() {
CycleInfo cycle = new CycleInfo(ImmutableList.of(SkyKey.create(SkyFunctionName.create("PATH"), 1234)), ImmutableList.of(SkyKey.create(SkyFunctionName.create("CYCLE"), 4321)));
ErrorInfo cycleErrorInfo = ErrorInfo.fromCycle(cycle);
Exception exception1 = new IOException("ehhhhh");
SkyKey causeOfException1 = SkyKey.create(SkyFunctionName.create("CAUSE1"), 1234);
DummySkyFunctionException dummyException1 = new DummySkyFunctionException(exception1, /*isTransient=*/
true, /*isCatastrophic=*/
false);
ErrorInfo exceptionErrorInfo1 = ErrorInfo.fromException(new ReifiedSkyFunctionException(dummyException1, causeOfException1), /*isTransitivelyTransient=*/
false);
// N.B this ErrorInfo will be catastrophic.
Exception exception2 = new IOException("blahhhhh");
SkyKey causeOfException2 = SkyKey.create(SkyFunctionName.create("CAUSE2"), 5678);
DummySkyFunctionException dummyException2 = new DummySkyFunctionException(exception2, /*isTransient=*/
false, /*isCatastrophic=*/
true);
ErrorInfo exceptionErrorInfo2 = ErrorInfo.fromException(new ReifiedSkyFunctionException(dummyException2, causeOfException2), /*isTransitivelyTransient=*/
false);
SkyKey currentKey = SkyKey.create(SkyFunctionName.create("CURRENT"), 9876);
ErrorInfo errorInfo = ErrorInfo.fromChildErrors(currentKey, ImmutableList.of(cycleErrorInfo, exceptionErrorInfo1, exceptionErrorInfo2));
assertThat(errorInfo.getRootCauses()).containsExactly(causeOfException1, causeOfException2);
// For simplicity we test the current implementation detail that we choose the first non-null
// (exception, cause) pair that we encounter. This isn't necessarily a requirement of the
// interface, but it makes the test convenient and is a way to document the current behavior.
assertThat(errorInfo.getException()).isSameAs(exception1);
assertThat(errorInfo.getRootCauseOfException()).isSameAs(causeOfException1);
assertThat(errorInfo.getCycleInfo()).containsExactly(new CycleInfo(ImmutableList.of(currentKey, Iterables.getOnlyElement(cycle.getPathToCycle())), cycle.getCycle()));
assertThat(errorInfo.isTransient()).isTrue();
assertThat(errorInfo.isCatastrophic()).isTrue();
}
use of com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException in project bazel by bazelbuild.
the class InMemoryNodeEntryTest method noPruneWhenDetailsChange.
@Test
public void noPruneWhenDetailsChange() throws InterruptedException {
NodeEntry entry = new InMemoryNodeEntry();
// Start evaluation.
entry.addReverseDepAndCheckIfDone(null);
SkyKey dep = key("dep");
addTemporaryDirectDep(entry, dep);
entry.signalDep();
setValue(entry, new IntegerValue(5), /*errorInfo=*/
null, /*graphVersion=*/
0L);
assertFalse(entry.isDirty());
assertTrue(entry.isDone());
entry.markDirty(/*isChanged=*/
false);
assertTrue(entry.isDirty());
assertFalse(entry.isChanged());
assertFalse(entry.isDone());
assertThatNodeEntry(entry).addReverseDepAndCheckIfDone(null).isEqualTo(DependencyState.NEEDS_SCHEDULING);
assertTrue(entry.isReady());
SkyKey parent = key("parent");
entry.addReverseDepAndCheckIfDone(parent);
assertEquals(NodeEntry.DirtyState.CHECK_DEPENDENCIES, entry.getDirtyState());
assertThat(entry.getNextDirtyDirectDeps()).containsExactly(dep);
addTemporaryDirectDep(entry, dep);
entry.signalDep(IntVersion.of(1L));
assertEquals(NodeEntry.DirtyState.NEEDS_REBUILDING, entry.getDirtyState());
assertThatNodeEntry(entry).hasTemporaryDirectDepsThat().containsExactly(dep);
ReifiedSkyFunctionException exception = new ReifiedSkyFunctionException(new GenericFunctionException(new SomeErrorException("oops"), Transience.PERSISTENT), key("cause"));
entry.markRebuilding();
setValue(entry, new IntegerValue(5), ErrorInfo.fromException(exception, false), /*graphVersion=*/
1L);
assertTrue(entry.isDone());
assertEquals("Version increments when setValue changes", IntVersion.of(1), entry.getVersion());
}
use of com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException in project bazel by bazelbuild.
the class InMemoryNodeEntryTest method errorAndValue.
@Test
public void errorAndValue() throws InterruptedException {
NodeEntry entry = new InMemoryNodeEntry();
// Start evaluation.
entry.addReverseDepAndCheckIfDone(null);
ReifiedSkyFunctionException exception = new ReifiedSkyFunctionException(new GenericFunctionException(new SomeErrorException("oops"), Transience.PERSISTENT), key("cause"));
ErrorInfo errorInfo = ErrorInfo.fromException(exception, false);
setValue(entry, new SkyValue() {
}, errorInfo, /*graphVersion=*/
0L);
assertTrue(entry.isDone());
assertEquals(errorInfo, entry.getErrorInfo());
}
Aggregations