aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar mschaller <mschaller@google.com>2018-07-01 19:41:03 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-07-01 19:42:30 -0700
commitb9166943e5dc23f7ce8ef2dce3cb98e5ce26e50b (patch)
tree1a25cc094c95b4cbed711712256683e138038114
parent1e54246d27417dd69aa5553da9e945254c492cd0 (diff)
Restart node building if previous dep is dirty, fix check-then-act races
While initializing the SkyFunctionEnvironment for a node being built, if a previously requested dep is found to be not done, reset and re-enqueue the building node. This lets the node handle the not-done dep like any other not-done dep (e.g. by enqueuing it or by waiting to be signalled by it). Similarly, while registering newly requested deps when building a node yields a value or an error, if a newly requested dep is found to be not done, return without completing the node, so that it may be signalled by the dep (without crashing; done nodes cannot be signalled). Also fixes a handful of remaining check-then-act races during Skyframe evaluation that were vulnerable to done->dirty node transitions. (Note that done->dirty node transitions during evaluation are planned, but not yet possible.) RELNOTES: None. PiperOrigin-RevId: 202886360
-rw-r--r--src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java223
-rw-r--r--src/main/java/com/google/devtools/build/skyframe/EvaluationSuccessStateSupplier.java7
-rw-r--r--src/main/java/com/google/devtools/build/skyframe/GraphInconsistencyReceiver.java3
-rw-r--r--src/main/java/com/google/devtools/build/skyframe/SimpleCycleDetector.java22
-rw-r--r--src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java62
5 files changed, 220 insertions, 97 deletions
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 31477b0ccb..1a2cab9b4b 100644
--- a/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/AbstractParallelEvaluator.java
@@ -33,6 +33,7 @@ import com.google.devtools.build.skyframe.NodeEntry.DependencyState;
import com.google.devtools.build.skyframe.NodeEntry.DirtyState;
import com.google.devtools.build.skyframe.ParallelEvaluatorContext.EnqueueParentBehavior;
import com.google.devtools.build.skyframe.QueryableGraph.Reason;
+import com.google.devtools.build.skyframe.SkyFunctionEnvironment.UndonePreviouslyRequestedDep;
import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
import java.time.Duration;
import java.util.Collection;
@@ -226,26 +227,31 @@ public abstract class AbstractParallelEvaluator {
Map<SkyKey, ? extends NodeEntry> entriesToCheck =
graph.getBatch(skyKey, Reason.OTHER, directDepsToCheck);
for (Map.Entry<SkyKey, ? extends NodeEntry> entry : entriesToCheck.entrySet()) {
- if (entry.getValue().isDone() && entry.getValue().getErrorInfo() != null) {
- // If any child has an error, we arbitrarily add a dep on the first one (needed
- // for error bubbling) and throw an exception coming from it.
- SkyKey errorKey = entry.getKey();
- NodeEntry errorEntry = entry.getValue();
- state.addTemporaryDirectDeps(GroupedListHelper.create(errorKey));
- errorEntry.checkIfDoneForDirtyReverseDep(skyKey);
- // Perform the necessary bookkeeping for any deps that are not being used.
- for (Map.Entry<SkyKey, ? extends NodeEntry> depEntry : entriesToCheck.entrySet()) {
- if (!depEntry.getKey().equals(errorKey)) {
- depEntry.getValue().removeReverseDep(skyKey);
- }
- }
- if (!evaluatorContext.getVisitor().preventNewEvaluations()) {
- // An error was already thrown in the evaluator. Don't do anything here.
- return DirtyOutcome.ALREADY_PROCESSED;
+ NodeEntry nodeEntryToCheck = entry.getValue();
+ SkyValue valueMaybeWithMetadata = nodeEntryToCheck.getValueMaybeWithMetadata();
+ if (valueMaybeWithMetadata == null) {
+ continue;
+ }
+ ErrorInfo maybeErrorInfo = ValueWithMetadata.getMaybeErrorInfo(valueMaybeWithMetadata);
+ if (maybeErrorInfo == null) {
+ continue;
+ }
+ // This child has an error. We add a dep from this node to it and throw an exception
+ // coming from it.
+ SkyKey errorKey = entry.getKey();
+ state.addTemporaryDirectDeps(GroupedListHelper.create(errorKey));
+ nodeEntryToCheck.checkIfDoneForDirtyReverseDep(skyKey);
+ // Perform the necessary bookkeeping for any deps that are not being used.
+ for (Map.Entry<SkyKey, ? extends NodeEntry> depEntry : entriesToCheck.entrySet()) {
+ if (!depEntry.getKey().equals(errorKey)) {
+ depEntry.getValue().removeReverseDep(skyKey);
}
- throw SchedulerException.ofError(
- errorEntry.getErrorInfo(), entry.getKey(), ImmutableSet.of(skyKey));
}
+ if (!evaluatorContext.getVisitor().preventNewEvaluations()) {
+ // An error was already thrown in the evaluator. Don't do anything here.
+ return DirtyOutcome.ALREADY_PROCESSED;
+ }
+ throw SchedulerException.ofError(maybeErrorInfo, errorKey, ImmutableSet.of(skyKey));
}
}
// It is safe to add these deps back to the node -- even if one of them has changed, the
@@ -359,8 +365,15 @@ public abstract class AbstractParallelEvaluator {
try {
evaluatorContext.getProgressReceiver().stateStarting(skyKey,
NodeState.INITIALIZING_ENVIRONMENT);
- env = new SkyFunctionEnvironment(skyKey, state.getTemporaryDirectDeps(), oldDeps,
- evaluatorContext);
+ env =
+ new SkyFunctionEnvironment(
+ skyKey, state.getTemporaryDirectDeps(), oldDeps, evaluatorContext);
+ } catch (UndonePreviouslyRequestedDep undonePreviouslyRequestedDep) {
+ // If a previously requested dep is no longer done, restart this node from scratch.
+ maybeEraseNodeToRestartFromScratch(
+ skyKey, state, SkyFunction.SENTINEL_FOR_RESTART_FROM_SCRATCH);
+ evaluatorContext.getVisitor().enqueueEvaluation(skyKey);
+ return;
} finally {
evaluatorContext.getProgressReceiver().stateEnding(skyKey,
NodeState.INITIALIZING_ENVIRONMENT, -1);
@@ -417,23 +430,31 @@ public abstract class AbstractParallelEvaluator {
return;
} else {
logger.warning(
- "Aborting evaluation due to "
- + builderException
- + " while evaluating "
- + skyKey);
+ String.format(
+ "Aborting evaluation due to %s while evaluating %s",
+ builderException, skyKey));
}
}
+ if (maybeHandleRegisteringNewlyDiscoveredDepsForDoneEntry(
+ skyKey, state, oldDeps, env, evaluatorContext.keepGoing())) {
+ // A newly requested dep transitioned from done to dirty before this node finished.
+ // If shouldFailFast is true, this node won't be signalled by any such newly dirtied
+ // dep (because new evaluations have been prevented), and this node is responsible for
+ // throwing the SchedulerException below.
+ // Otherwise, this node will be signalled again, and so we should return.
+ if (!shouldFailFast) {
+ return;
+ }
+ }
boolean isTransitivelyTransient =
reifiedBuilderException.isTransient()
|| env.isAnyDirectDepErrorTransitivelyTransient()
|| env.isAnyNewlyRequestedDepErrorTransitivelyTransient();
- ErrorInfo errorInfo = evaluatorContext.getErrorInfoManager().fromException(
- skyKey,
- reifiedBuilderException,
- isTransitivelyTransient);
- registerNewlyDiscoveredDepsForDoneEntry(
- skyKey, state, oldDeps, env, evaluatorContext.keepGoing());
+ ErrorInfo errorInfo =
+ evaluatorContext
+ .getErrorInfoManager()
+ .fromException(skyKey, reifiedBuilderException, isTransitivelyTransient);
env.setError(state, errorInfo);
Set<SkyKey> rdepsToBubbleUpTo =
env.commit(
@@ -475,9 +496,13 @@ public abstract class AbstractParallelEvaluator {
try {
evaluatorContext.getProgressReceiver().stateStarting(skyKey, NodeState.COMMIT);
+ if (maybeHandleRegisteringNewlyDiscoveredDepsForDoneEntry(
+ skyKey, state, oldDeps, env, evaluatorContext.keepGoing())) {
+ // A newly requested dep transitioned from done to dirty before this node finished.
+ // This node will be signalled again, and so we should return.
+ return;
+ }
env.setValue(value);
- registerNewlyDiscoveredDepsForDoneEntry(
- skyKey, state, oldDeps, env, evaluatorContext.keepGoing());
env.commit(state, EnqueueParentBehavior.ENQUEUE);
} finally {
evaluatorContext.getProgressReceiver().stateEnding(skyKey, NodeState.COMMIT, -1);
@@ -485,14 +510,14 @@ public abstract class AbstractParallelEvaluator {
return;
}
- if (env.getDepErrorKey() != null) {
+ SkyKey childErrorKey = env.getDepErrorKey();
+ if (childErrorKey != null) {
Preconditions.checkState(
- !evaluatorContext.keepGoing(), "%s %s %s", skyKey, state, env.getDepErrorKey());
+ !evaluatorContext.keepGoing(), "%s %s %s", skyKey, state, childErrorKey);
// We encountered a child error in noKeepGoing mode, so we want to fail fast. But we first
// need to add the edge between the current node and the child error it requested so that
// error bubbling can occur. Note that this edge will subsequently be removed during graph
// cleaning (since the current node will never be committed to the graph).
- SkyKey childErrorKey = env.getDepErrorKey();
NodeEntry childErrorEntry =
Preconditions.checkNotNull(
graph.get(skyKey, Reason.OTHER, childErrorKey),
@@ -511,15 +536,31 @@ public abstract class AbstractParallelEvaluator {
} else {
childErrorState = childErrorEntry.addReverseDepAndCheckIfDone(skyKey);
}
- Preconditions.checkState(
- childErrorState == DependencyState.DONE,
- "skyKey: %s, state: %s childErrorKey: %s",
- skyKey,
- state,
- childErrorKey,
- childErrorEntry);
+ if (childErrorState != DependencyState.DONE) {
+ // The child in error may have transitioned from done to dirty between when this node
+ // discovered the error and now. Notify the graph inconsistency receiver so that we
+ // can crash if that's unexpected.
+ // We don't enqueue the child, even if it returns NEEDS_SCHEDULING, because we are
+ // about to shut down evaluation.
+ evaluatorContext
+ .getGraphInconsistencyReceiver()
+ .noteInconsistencyAndMaybeThrow(
+ skyKey, childErrorKey, Inconsistency.CHILD_UNDONE_FOR_BUILDING_NODE);
+ }
}
- ErrorInfo childErrorInfo = Preconditions.checkNotNull(childErrorEntry.getErrorInfo());
+ SkyValue childErrorInfoMaybe =
+ Preconditions.checkNotNull(
+ env.maybeGetValueFromErrorOrDeps(childErrorKey),
+ "dep error found but then lost while building: %s %s",
+ skyKey,
+ childErrorKey);
+ ErrorInfo childErrorInfo =
+ Preconditions.checkNotNull(
+ ValueWithMetadata.getMaybeErrorInfo(childErrorInfoMaybe),
+ "dep error found but then wasn't an error while building: %s %s %s",
+ skyKey,
+ childErrorKey,
+ childErrorInfoMaybe);
evaluatorContext.getVisitor().preventNewEvaluations();
throw SchedulerException.ofError(childErrorInfo, childErrorKey, ImmutableSet.of(skyKey));
}
@@ -652,16 +693,28 @@ public abstract class AbstractParallelEvaluator {
}
/**
- * Add any additional deps that were registered during the run of a builder that finished by
- * creating a node or throwing an error. Builders may throw errors even if all their deps were not
- * provided -- we trust that a SkyFunction might know it should throw an error even if not all of
- * its requested deps are done. However, that means we're assuming the SkyFunction would throw
- * that same error if all of its requested deps were done. Unfortunately, there is no way to
- * enforce that condition.
+ * Add any newly discovered deps that were registered during the run of a SkyFunction that
+ * finished by returning a value or throwing an error. SkyFunctions may throw errors even if all
+ * their deps were not provided -- we trust that a SkyFunction might know it should throw an error
+ * even if not all of its requested deps are done. However, that means we're assuming the
+ * SkyFunction would throw that same error if all of its requested deps were done. Unfortunately,
+ * there is no way to enforce that condition.
+ *
+ * <p>Returns {@code true} if any newly discovered dep is dirty when this node registers itself as
+ * an rdep.
+ *
+ * <p>This can happen if a newly discovered dep transitions from done to dirty between when this
+ * node's evaluation accessed the dep's value and here. Adding this node as an rdep of that dep
+ * (or checking that this node is an rdep of that dep) will cause this node to be signalled when
+ * that dep completes.
+ *
+ * <p>If this returns {@code true}, this node should not actually finish, and this evaluation
+ * attempt should make no changes to the node after this method returns, because a completing dep
+ * may schedule a new evaluation attempt at any time.
*
* @throws InterruptedException
*/
- private void registerNewlyDiscoveredDepsForDoneEntry(
+ private boolean maybeHandleRegisteringNewlyDiscoveredDepsForDoneEntry(
SkyKey skyKey,
NodeEntry entry,
Set<SkyKey> oldDeps,
@@ -670,7 +723,7 @@ public abstract class AbstractParallelEvaluator {
throws InterruptedException {
Iterator<SkyKey> it = env.getNewlyRequestedDeps().iterator();
if (!it.hasNext()) {
- return;
+ return false;
}
// We don't expect any unfinished deps in a keep-going build.
@@ -685,43 +738,57 @@ public abstract class AbstractParallelEvaluator {
InterruptibleSupplier<Map<SkyKey, ? extends NodeEntry>> newlyAddedNewDepNodes =
graph.getBatchAsync(skyKey, Reason.RDEP_ADDITION, newlyAddedNewDeps);
+ // Note that the depEntries in the following two loops can't be null. In a keep-going build, we
+ // normally expect all deps to be done. In a non-keep-going build, If env.newlyRequestedDeps
+ // contained a key for a node that wasn't done, then it would have been removed via
+ // removeUndoneNewlyRequestedDeps() just above this loop. However, with intra-evaluation
+ // dirtying, a dep may not be done.
+ boolean dirtyDepFound = false;
for (Map.Entry<SkyKey, ? extends NodeEntry> newDep :
graph.getBatch(skyKey, Reason.SIGNAL_DEP, previouslyRegisteredNewDeps).entrySet()) {
- // Note that this depEntry can't be null. In a keep-going build, we expect all deps to be
- // done. In a non-keep-going build, If env.newlyRequestedDeps contained a key with a
- // null entry, then it would have been added to unfinishedDeps and then removed from
- // env.newlyRequestedDeps just above this loop.
- NodeEntry depEntry = newDep.getValue();
- DependencyState triState = depEntry.checkIfDoneForDirtyReverseDep(skyKey);
- Preconditions.checkState(
- DependencyState.DONE == triState,
- "new dep %s was not already done for %s. NodeEntry: %s. DepNodeEntry: %s",
- newDep,
- skyKey,
- entry,
- depEntry);
- entry.signalDep();
+ DependencyState triState = newDep.getValue().checkIfDoneForDirtyReverseDep(skyKey);
+ if (maybeHandleUndoneDepForDoneEntry(entry, triState, skyKey, newDep.getKey())) {
+ dirtyDepFound = true;
+ }
}
for (SkyKey newDep : newlyAddedNewDeps) {
- // Note that this depEntry can't be null. In a keep-going build, we expect all deps to be
- // done. In a non-keep-going build, If env.newlyRequestedDeps contained a key with a
- // null entry, then it would have been added to unfinishedDeps and then removed from
- // env.newlyRequestedDeps just above this loop.
NodeEntry depEntry =
Preconditions.checkNotNull(newlyAddedNewDepNodes.get().get(newDep), newDep);
DependencyState triState = depEntry.addReverseDepAndCheckIfDone(skyKey);
- Preconditions.checkState(
- DependencyState.DONE == triState,
- "new dep %s was not already done for %s. NodeEntry: %s. DepNodeEntry: %s",
- newDep,
- skyKey,
- entry,
- depEntry);
- entry.signalDep();
+ if (maybeHandleUndoneDepForDoneEntry(entry, triState, skyKey, newDep)) {
+ dirtyDepFound = true;
+ }
}
+
Preconditions.checkState(
- entry.isReady(), "%s %s %s", skyKey, entry, env.getNewlyRequestedDeps());
+ dirtyDepFound || entry.isReady(), "%s %s %s", skyKey, entry, env.getNewlyRequestedDeps());
+ return dirtyDepFound;
+ }
+
+ /**
+ * Returns {@code true} if the dep was not done. Notifies the {@link GraphInconsistencyReceiver}
+ * if so. Schedules the dep for evaluation if necessary.
+ *
+ * <p>Otherwise, returns {@code false} and signals this node.
+ */
+ private boolean maybeHandleUndoneDepForDoneEntry(
+ NodeEntry entry, DependencyState triState, SkyKey skyKey, SkyKey depKey) {
+ if (triState == DependencyState.DONE) {
+ entry.signalDep();
+ return false;
+ }
+ // The dep may have transitioned from done to dirty between when this node read its value and
+ // now. Notify the graph inconsistency receiver so that we can crash if that's unexpected. We
+ // schedule the dep if it needs scheduling, because nothing else can if we don't.
+ evaluatorContext
+ .getGraphInconsistencyReceiver()
+ .noteInconsistencyAndMaybeThrow(
+ skyKey, depKey, Inconsistency.CHILD_UNDONE_FOR_BUILDING_NODE);
+ if (triState == DependencyState.NEEDS_SCHEDULING) {
+ evaluatorContext.getVisitor().enqueueEvaluation(depKey);
+ }
+ return true;
}
/**
diff --git a/src/main/java/com/google/devtools/build/skyframe/EvaluationSuccessStateSupplier.java b/src/main/java/com/google/devtools/build/skyframe/EvaluationSuccessStateSupplier.java
index f4c320ca6e..181727b2ad 100644
--- a/src/main/java/com/google/devtools/build/skyframe/EvaluationSuccessStateSupplier.java
+++ b/src/main/java/com/google/devtools/build/skyframe/EvaluationSuccessStateSupplier.java
@@ -14,6 +14,7 @@
package com.google.devtools.build.skyframe;
import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
import com.google.devtools.build.skyframe.EvaluationProgressReceiver.EvaluationSuccessState;
/**
@@ -42,4 +43,10 @@ class EvaluationSuccessStateSupplier implements Supplier<EvaluationSuccessState>
e);
}
}
+
+ static Supplier<EvaluationSuccessState> fromSkyValue(SkyValue value) {
+ return ValueWithMetadata.justValue(value) != null
+ ? Suppliers.ofInstance(EvaluationSuccessState.SUCCESS)
+ : Suppliers.ofInstance(EvaluationSuccessState.FAILURE);
+ }
}
diff --git a/src/main/java/com/google/devtools/build/skyframe/GraphInconsistencyReceiver.java b/src/main/java/com/google/devtools/build/skyframe/GraphInconsistencyReceiver.java
index 6aa161ab40..5d99c7d2ad 100644
--- a/src/main/java/com/google/devtools/build/skyframe/GraphInconsistencyReceiver.java
+++ b/src/main/java/com/google/devtools/build/skyframe/GraphInconsistencyReceiver.java
@@ -30,7 +30,8 @@ public interface GraphInconsistencyReceiver {
/** The type of inconsistency detected. */
enum Inconsistency {
RESET_REQUESTED,
- CHILD_MISSING_FOR_DIRTY_NODE
+ CHILD_MISSING_FOR_DIRTY_NODE,
+ CHILD_UNDONE_FOR_BUILDING_NODE
}
/** A {@link GraphInconsistencyReceiver} that crashes on any inconsistency. */
diff --git a/src/main/java/com/google/devtools/build/skyframe/SimpleCycleDetector.java b/src/main/java/com/google/devtools/build/skyframe/SimpleCycleDetector.java
index d1348daab2..cf82baf125 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SimpleCycleDetector.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SimpleCycleDetector.java
@@ -27,6 +27,7 @@ import com.google.devtools.build.lib.profiler.AutoProfiler;
import com.google.devtools.build.lib.util.GroupedList;
import com.google.devtools.build.skyframe.ParallelEvaluatorContext.EnqueueParentBehavior;
import com.google.devtools.build.skyframe.QueryableGraph.Reason;
+import com.google.devtools.build.skyframe.SkyFunctionEnvironment.UndonePreviouslyRequestedDep;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
@@ -153,12 +154,21 @@ public class SimpleCycleDetector implements CycleDetector {
"Node %s was not successfully evaluated, but had no child errors. NodeEntry: %s",
key,
entry);
- SkyFunctionEnvironment env =
- new SkyFunctionEnvironment(
- key,
- directDeps,
- Sets.difference(entry.getAllRemainingDirtyDirectDeps(), removedDeps),
- evaluatorContext);
+ SkyFunctionEnvironment env = null;
+ try {
+ env =
+ new SkyFunctionEnvironment(
+ key,
+ directDeps,
+ Sets.difference(entry.getAllRemainingDirtyDirectDeps(), removedDeps),
+ evaluatorContext);
+ } catch (UndonePreviouslyRequestedDep undoneDep) {
+ // All children were finished according to the CHILDREN_FINISHED sentinel, and cycle
+ // detection does not do normal SkyFunction evaluation, so no restarting nor child
+ // dirtying was possible.
+ throw new IllegalStateException(
+ "Previously requested dep not done: " + undoneDep.getDepKey(), undoneDep);
+ }
env.setError(entry, ErrorInfo.fromChildErrors(key, errorDeps));
env.commit(entry, EnqueueParentBehavior.SIGNAL);
} else {
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 6d7918f768..4b2fc51cdf 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunctionEnvironment.java
@@ -30,6 +30,7 @@ import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.util.GroupedList;
import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper;
import com.google.devtools.build.skyframe.EvaluationProgressReceiver.EvaluationState;
+import com.google.devtools.build.skyframe.GraphInconsistencyReceiver.Inconsistency;
import com.google.devtools.build.skyframe.NodeEntry.DependencyState;
import com.google.devtools.build.skyframe.ParallelEvaluatorContext.EnqueueParentBehavior;
import com.google.devtools.build.skyframe.QueryableGraph.Reason;
@@ -149,14 +150,24 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
GroupedList<SkyKey> directDeps,
Set<SkyKey> oldDeps,
ParallelEvaluatorContext evaluatorContext)
- throws InterruptedException {
- this(skyKey, directDeps, null, oldDeps, evaluatorContext);
+ throws InterruptedException, UndonePreviouslyRequestedDep {
+ super(directDeps);
+ this.skyKey = skyKey;
+ this.oldDeps = oldDeps;
+ this.evaluatorContext = evaluatorContext;
+ this.bubbleErrorInfo = null;
+ this.previouslyRequestedDepsValues =
+ batchPrefetch(skyKey, directDeps, oldDeps, /*assertDone=*/ true, skyKey);
+ Preconditions.checkState(
+ !this.previouslyRequestedDepsValues.containsKey(ErrorTransienceValue.KEY),
+ "%s cannot have a dep on ErrorTransienceValue during building",
+ skyKey);
}
SkyFunctionEnvironment(
SkyKey skyKey,
GroupedList<SkyKey> directDeps,
- @Nullable Map<SkyKey, ValueWithMetadata> bubbleErrorInfo,
+ Map<SkyKey, ValueWithMetadata> bubbleErrorInfo,
Set<SkyKey> oldDeps,
ParallelEvaluatorContext evaluatorContext)
throws InterruptedException {
@@ -164,9 +175,14 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
this.skyKey = skyKey;
this.oldDeps = oldDeps;
this.evaluatorContext = evaluatorContext;
- this.previouslyRequestedDepsValues =
- batchPrefetch(skyKey, directDeps, oldDeps, /*assertDone=*/ bubbleErrorInfo == null, skyKey);
- this.bubbleErrorInfo = bubbleErrorInfo;
+ this.bubbleErrorInfo = Preconditions.checkNotNull(bubbleErrorInfo);
+ try {
+ this.previouslyRequestedDepsValues =
+ batchPrefetch(skyKey, directDeps, oldDeps, /*assertDone=*/ false, skyKey);
+ } catch (UndonePreviouslyRequestedDep undonePreviouslyRequestedDep) {
+ throw new IllegalStateException(
+ "batchPrefetch can't throw UndonePreviouslyRequestedDep unless assertDone is true");
+ }
Preconditions.checkState(
!this.previouslyRequestedDepsValues.containsKey(ErrorTransienceValue.KEY),
"%s cannot have a dep on ErrorTransienceValue during building",
@@ -179,7 +195,7 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
Set<SkyKey> oldDeps,
boolean assertDone,
SkyKey keyForDebugging)
- throws InterruptedException {
+ throws InterruptedException, UndonePreviouslyRequestedDep {
Set<SkyKey> depKeysAsSet = null;
if (PREFETCH_OLD_DEPS) {
if (!oldDeps.isEmpty()) {
@@ -211,9 +227,15 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
ImmutableMap.builderWithExpectedSize(batchMap.size());
for (Entry<SkyKey, ? extends NodeEntry> entry : batchMap.entrySet()) {
SkyValue valueMaybeWithMetadata = entry.getValue().getValueMaybeWithMetadata();
- if (assertDone) {
- Preconditions.checkNotNull(
- valueMaybeWithMetadata, "%s had not done %s", keyForDebugging, entry);
+ if (assertDone && valueMaybeWithMetadata == null) {
+ // A previously requested dep may have transitioned from done to dirty between when the node
+ // was read during a previous attempt to build this node and now. Notify the graph
+ // inconsistency receiver so that we can crash if that's unexpected.
+ evaluatorContext
+ .getGraphInconsistencyReceiver()
+ .noteInconsistencyAndMaybeThrow(
+ skyKey, entry.getKey(), Inconsistency.CHILD_UNDONE_FOR_BUILDING_NODE);
+ throw new UndonePreviouslyRequestedDep(entry.getKey());
}
depValuesBuilder.put(
entry.getKey(), valueMaybeWithMetadata == null ? NULL_MARKER : valueMaybeWithMetadata);
@@ -407,6 +429,9 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
newlyRequestedDepsValues.put(key, valueOrNullMarker);
if (valueOrNullMarker == NULL_MARKER) {
// TODO(mschaller): handle registered deps that transitioned from done to dirty during eval
+ // But how? Restarting the current node may not help, because this dep was *registered*, not
+ // requested. For now, no node that gets registered as a dep is eligible for
+ // intra-evaluation dirtying, so let it crash.
Preconditions.checkState(!assertDone, "%s had not done: %s", skyKey, key);
continue;
}
@@ -428,7 +453,7 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
* (Note that none of the maps can have {@code null} as a value.)
*/
@Nullable
- private SkyValue maybeGetValueFromErrorOrDeps(SkyKey key) {
+ SkyValue maybeGetValueFromErrorOrDeps(SkyKey key) {
if (bubbleErrorInfo != null) {
ValueWithMetadata bubbleErrorInfoValue = bubbleErrorInfo.get(key);
if (bubbleErrorInfoValue != null) {
@@ -713,7 +738,7 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
.evaluated(
skyKey,
evaluationState == EvaluationState.BUILT ? value : null,
- new EvaluationSuccessStateSupplier(primaryEntry),
+ EvaluationSuccessStateSupplier.fromSkyValue(valueWithMetadata),
evaluationState);
evaluatorContext.signalValuesAndEnqueueIfReady(
@@ -756,4 +781,17 @@ class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
}
newlyRequestedDeps.endGroup();
}
+
+ /** Thrown during environment construction if a previously requested dep is no longer done. */
+ static class UndonePreviouslyRequestedDep extends Exception {
+ private final SkyKey depKey;
+
+ UndonePreviouslyRequestedDep(SkyKey depKey) {
+ this.depKey = depKey;
+ }
+
+ SkyKey getDepKey() {
+ return depKey;
+ }
+ }
}