blob: 850b12495ce5d4428af36ce192f420415c32f269 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.skyframe;
15
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010016import com.google.common.base.Preconditions;
17import com.google.common.base.Predicate;
18import com.google.common.base.Predicates;
Mark Schaller4752dbb2015-08-20 18:57:44 +000019import com.google.common.base.Supplier;
20import com.google.common.base.Suppliers;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.base.Throwables;
22import com.google.common.collect.ImmutableList;
23import com.google.common.collect.ImmutableMap;
24import com.google.common.collect.ImmutableSet;
25import com.google.common.collect.Interner;
26import com.google.common.collect.Interners;
27import com.google.common.collect.Iterables;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.common.collect.Sets;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029import com.google.devtools.build.lib.collect.nestedset.NestedSet;
30import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
31import com.google.devtools.build.lib.collect.nestedset.NestedSetVisitor;
32import com.google.devtools.build.lib.concurrent.AbstractQueueVisitor;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010034import com.google.devtools.build.lib.events.Event;
35import com.google.devtools.build.lib.events.EventHandler;
36import com.google.devtools.build.lib.events.StoredEventHandler;
37import com.google.devtools.build.lib.profiler.Profiler;
38import com.google.devtools.build.lib.profiler.ProfilerTask;
39import com.google.devtools.build.lib.util.GroupedList.GroupedListHelper;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010040import com.google.devtools.build.skyframe.EvaluationProgressReceiver.EvaluationState;
Janak Ramakrishnan39ad9662015-07-15 12:02:53 +000041import com.google.devtools.build.skyframe.MemoizingEvaluator.EmittedEventState;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042import com.google.devtools.build.skyframe.NodeEntry.DependencyState;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000043import com.google.devtools.build.skyframe.NodeEntry.DirtyState;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044import com.google.devtools.build.skyframe.Scheduler.SchedulerException;
45import com.google.devtools.build.skyframe.SkyFunctionException.ReifiedSkyFunctionException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046
47import java.util.ArrayDeque;
48import java.util.ArrayList;
49import java.util.Collection;
50import java.util.Collections;
51import java.util.Deque;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.Iterator;
55import java.util.LinkedHashSet;
56import java.util.List;
57import java.util.Map;
58import java.util.Set;
59import java.util.concurrent.CountDownLatch;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010060import java.util.concurrent.TimeUnit;
61import java.util.concurrent.atomic.AtomicBoolean;
62
63import javax.annotation.Nullable;
64
65/**
66 * Evaluates a set of given functions ({@code SkyFunction}s) with arguments ({@code SkyKey}s).
67 * Cycles are not allowed and are detected during the traversal.
68 *
69 * <p>This class implements multi-threaded evaluation. This is a fairly complex process that has
70 * strong consistency requirements between the {@link ProcessableGraph}, the nodes in the graph of
71 * type {@link NodeEntry}, the work queue, and the set of in-flight nodes.
72 *
73 * <p>The basic invariants are:
74 *
75 * <p>A node can be in one of three states: ready, waiting, and done. A node is ready if and only
76 * if all of its dependencies have been signaled. A node is done if it has a value. It is waiting
77 * if not all of its dependencies have been signaled.
78 *
79 * <p>A node must be in the work queue if and only if it is ready. It is an error for a node to be
80 * in the work queue twice at the same time.
81 *
82 * <p>A node is considered in-flight if it has been created, and is not done yet. In case of an
83 * interrupt, the work queue is discarded, and the in-flight set is used to remove partially
84 * computed values.
85 *
86 * <p>Each evaluation of the graph takes place at a "version," which is currently given by a
87 * non-negative {@code long}. The version can also be thought of as an "mtime." Each node in the
88 * graph has a version, which is the last version at which its value changed. This version data is
89 * used to avoid unnecessary re-evaluation of values. If a node is re-evaluated and found to have
90 * the same data as before, its version (mtime) remains the same. If all of a node's children's
91 * have the same version as before, its re-evaluation can be skipped.
92 *
93 * <p>This class is not intended for direct use, and is only exposed as public for use in
94 * evaluation implementations outside of this package.
95 */
96public final class ParallelEvaluator implements Evaluator {
97 private final ProcessableGraph graph;
98 private final Version graphVersion;
99
100 private final Predicate<SkyKey> nodeEntryIsDone = new Predicate<SkyKey>() {
101 @Override
102 public boolean apply(SkyKey skyKey) {
103 return isDoneForBuild(graph.get(skyKey));
104 }
105 };
106
Mark Schaller4752dbb2015-08-20 18:57:44 +0000107 private static class SkyValueSupplier implements Supplier<SkyValue> {
108
109 private final NodeEntry state;
110
111 public SkyValueSupplier(NodeEntry state) {
112 this.state = state;
113 }
114
115 @Override
116 public SkyValue get() {
117 return state.getValue();
118 }
119 }
Michajlo Matijkiwaa058282015-09-28 22:13:27 +0000120
Mark Schaller986c5d62015-09-18 15:56:42 +0000121 /** An general interface for {@link ParallelEvaluator} to receive objects of type {@code T}. */
Damien Martin-Guillerez31e251e2015-09-11 11:40:28 +0000122 public interface Receiver<T> {
123 // TODO(dmarting): should we just make it a common object for all Bazel codebase?
124 /**
125 * Consumes the given object.
126 */
127 void accept(T object);
128 }
Mark Schaller4752dbb2015-08-20 18:57:44 +0000129
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100130 private final ImmutableMap<? extends SkyFunctionName, ? extends SkyFunction> skyFunctions;
131
132 private final EventHandler reporter;
133 private final NestedSetVisitor<TaggedEvents> replayingNestedSetEventVisitor;
134 private final boolean keepGoing;
135 private final int threadCount;
136 @Nullable private final EvaluationProgressReceiver progressReceiver;
137 private final DirtyKeyTracker dirtyKeyTracker;
Janak Ramakrishnan54111292015-09-09 21:44:07 +0000138 private final Receiver<Collection<SkyKey>> inflightKeysReceiver;
Janak Ramakrishnan39ad9662015-07-15 12:02:53 +0000139 private final Predicate<Event> storedEventFilter;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100140
141 private static final Interner<SkyKey> KEY_CANONICALIZER = Interners.newWeakInterner();
142
Janak Ramakrishnan54111292015-09-09 21:44:07 +0000143 public ParallelEvaluator(
144 ProcessableGraph graph,
145 Version graphVersion,
Janak Ramakrishnan39ad9662015-07-15 12:02:53 +0000146 ImmutableMap<? extends SkyFunctionName, ? extends SkyFunction> skyFunctions,
147 final EventHandler reporter,
148 EmittedEventState emittedEventState,
Janak Ramakrishnan54111292015-09-09 21:44:07 +0000149 Predicate<Event> storedEventFilter,
150 boolean keepGoing,
151 int threadCount,
Janak Ramakrishnan39ad9662015-07-15 12:02:53 +0000152 @Nullable EvaluationProgressReceiver progressReceiver,
Janak Ramakrishnan54111292015-09-09 21:44:07 +0000153 DirtyKeyTracker dirtyKeyTracker,
154 Receiver<Collection<SkyKey>> inflightKeysReceiver) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100155 this.graph = graph;
156 this.skyFunctions = skyFunctions;
157 this.graphVersion = graphVersion;
Janak Ramakrishnan54111292015-09-09 21:44:07 +0000158 this.inflightKeysReceiver = inflightKeysReceiver;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100159 this.reporter = Preconditions.checkNotNull(reporter);
160 this.keepGoing = keepGoing;
161 this.threadCount = threadCount;
162 this.progressReceiver = progressReceiver;
163 this.dirtyKeyTracker = Preconditions.checkNotNull(dirtyKeyTracker);
164 this.replayingNestedSetEventVisitor =
165 new NestedSetVisitor<>(new NestedSetEventReceiver(reporter), emittedEventState);
Janak Ramakrishnan39ad9662015-07-15 12:02:53 +0000166 this.storedEventFilter = storedEventFilter;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100167 }
168
169 /**
170 * Receives the events from the NestedSet and delegates to the reporter.
171 */
172 private static class NestedSetEventReceiver implements NestedSetVisitor.Receiver<TaggedEvents> {
173
174 private final EventHandler reporter;
175
176 public NestedSetEventReceiver(EventHandler reporter) {
177 this.reporter = reporter;
178 }
179 @Override
180 public void accept(TaggedEvents events) {
181 String tag = events.getTag();
182 for (Event e : events.getEvents()) {
183 reporter.handle(e.withTag(tag));
184 }
185 }
186 }
187
188 /**
189 * A suitable {@link SkyFunction.Environment} implementation.
190 */
Janak Ramakrishnan24f5e692015-04-10 19:39:55 +0000191 class SkyFunctionEnvironment extends AbstractSkyFunctionEnvironment {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100192 private boolean building = true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100193 private SkyKey depErrorKey = null;
194 private final SkyKey skyKey;
195 private SkyValue value = null;
196 private ErrorInfo errorInfo = null;
197 private final Map<SkyKey, ValueWithMetadata> bubbleErrorInfo;
198 /** The set of values previously declared as dependencies. */
199 private final Set<SkyKey> directDeps;
200
201 /**
202 * The grouped list of values requested during this build as dependencies. On a subsequent
203 * build, if this value is dirty, all deps in the same dependency group can be checked in
204 * parallel for changes. In other words, if dep1 and dep2 are in the same group, then dep1 will
205 * be checked in parallel with dep2. See {@link #getValues} for more.
206 */
207 private final GroupedListHelper<SkyKey> newlyRequestedDeps = new GroupedListHelper<>();
208
209 /**
210 * The value visitor managing the thread pool. Used to enqueue parents when this value is
211 * finished, and, during testing, to block until an exception is thrown if a value builder
212 * requests that.
213 */
214 private final ValueVisitor visitor;
215
216 /** The set of errors encountered while fetching children. */
217 private final Collection<ErrorInfo> childErrorInfos = new LinkedHashSet<>();
218 private final StoredEventHandler eventHandler = new StoredEventHandler() {
219 @Override
220 public void handle(Event e) {
221 checkActive();
Janak Ramakrishnan39ad9662015-07-15 12:02:53 +0000222 if (storedEventFilter.apply(e)) {
223 super.handle(e);
224 } else {
225 reporter.handle(e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100226 }
227 }
228 };
229
230 private SkyFunctionEnvironment(SkyKey skyKey, Set<SkyKey> directDeps, ValueVisitor visitor) {
231 this(skyKey, directDeps, null, visitor);
232 }
233
234 private SkyFunctionEnvironment(SkyKey skyKey, Set<SkyKey> directDeps,
235 @Nullable Map<SkyKey, ValueWithMetadata> bubbleErrorInfo, ValueVisitor visitor) {
236 this.skyKey = skyKey;
237 this.directDeps = Collections.unmodifiableSet(directDeps);
238 this.bubbleErrorInfo = bubbleErrorInfo;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100239 this.visitor = visitor;
240 }
241
242 private void checkActive() {
243 Preconditions.checkState(building, skyKey);
244 }
245
246 private NestedSet<TaggedEvents> buildEvents(boolean missingChildren) {
247 // Aggregate the nested set of events from the direct deps, also adding the events from
248 // building this value.
249 NestedSetBuilder<TaggedEvents> eventBuilder = NestedSetBuilder.stableOrder();
250 ImmutableList<Event> events = eventHandler.getEvents();
251 if (!events.isEmpty()) {
252 eventBuilder.add(new TaggedEvents(getTagFromKey(), events));
253 }
254 for (SkyKey dep : graph.get(skyKey).getTemporaryDirectDeps()) {
255 ValueWithMetadata value = getValueMaybeFromError(dep, bubbleErrorInfo);
256 if (value != null) {
257 eventBuilder.addTransitive(value.getTransitiveEvents());
258 } else {
259 Preconditions.checkState(missingChildren, "", dep, skyKey);
260 }
261 }
262 return eventBuilder.build();
263 }
264
265 /**
266 * If this node has an error, that is, if errorInfo is non-null, do nothing. Otherwise, set
267 * errorInfo to the union of the child errors that were recorded earlier by getValueOrException,
268 * if there are any.
269 */
270 private void finalizeErrorInfo() {
271 if (errorInfo == null && !childErrorInfos.isEmpty()) {
Michajlo Matijkiwaa058282015-09-28 22:13:27 +0000272 errorInfo = ErrorInfo.fromChildErrors(skyKey, childErrorInfos);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100273 }
274 }
275
276 private void setValue(SkyValue newValue) {
277 Preconditions.checkState(errorInfo == null && bubbleErrorInfo == null,
278 "%s %s %s %s", skyKey, newValue, errorInfo, bubbleErrorInfo);
279 Preconditions.checkState(value == null, "%s %s %s", skyKey, value, newValue);
280 value = newValue;
281 }
282
283 /**
284 * Set this node to be in error. The node's value must not have already been set. However, all
285 * dependencies of this node <i>must</i> already have been registered, since this method may
286 * register a dependence on the error transience node, which should always be the last dep.
287 */
288 private void setError(ErrorInfo errorInfo) {
289 Preconditions.checkState(value == null, "%s %s %s", skyKey, value, errorInfo);
290 Preconditions.checkState(this.errorInfo == null,
291 "%s %s %s", skyKey, this.errorInfo, errorInfo);
292
293 if (errorInfo.isTransient()) {
294 DependencyState triState =
295 graph.get(ErrorTransienceValue.key()).addReverseDepAndCheckIfDone(skyKey);
296 Preconditions.checkState(triState == DependencyState.DONE,
297 "%s %s %s", skyKey, triState, errorInfo);
298
299 final NodeEntry state = graph.get(skyKey);
300 state.addTemporaryDirectDeps(
301 GroupedListHelper.create(ImmutableList.of(ErrorTransienceValue.key())));
302 state.signalDep();
303 }
304
305 this.errorInfo = Preconditions.checkNotNull(errorInfo, skyKey);
306 }
307
Janak Ramakrishnan24f5e692015-04-10 19:39:55 +0000308 @Override
309 protected ImmutableMap<SkyKey, ValueOrUntypedException> getValueOrUntypedExceptions(
Michajlo Matijkiw805e4d92015-08-28 21:02:32 +0000310 Set<SkyKey> depKeys) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100311 checkActive();
Michajlo Matijkiw805e4d92015-08-28 21:02:32 +0000312 Set<SkyKey> keys = Sets.newLinkedHashSetWithExpectedSize(depKeys.size());
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000313 for (SkyKey depKey : depKeys) {
314 // Canonicalize SkyKeys to save memory.
315 keys.add(KEY_CANONICALIZER.intern(depKey));
316 }
317 depKeys = keys;
318 Map<SkyKey, ValueWithMetadata> values = getValuesMaybeFromError(depKeys, bubbleErrorInfo);
319 ImmutableMap.Builder<SkyKey, ValueOrUntypedException> builder = ImmutableMap.builder();
320 for (SkyKey depKey : depKeys) {
Janak Ramakrishnand76dfc12015-08-25 23:23:15 +0000321 Preconditions.checkState(!depKey.equals(ErrorTransienceValue.key()));
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000322 ValueWithMetadata value = values.get(depKey);
323 if (value == null) {
324 // If this entry is not yet done then (optionally) record the missing dependency and
325 // return null.
326 valuesMissing = true;
327 if (bubbleErrorInfo != null) {
328 // Values being built just for their errors don't get to request new children.
329 builder.put(depKey, ValueOrExceptionUtils.ofNull());
330 continue;
331 }
Janak Ramakrishnande281a72015-09-24 21:12:05 +0000332 if (directDeps.contains(depKey)) {
333 throw new IllegalStateException(
334 "Undone key "
335 + depKey
336 + " was already in deps of "
337 + skyKey
338 + "( dep: "
339 + graph.get(depKey)
340 + ", parent: "
341 + graph.get(skyKey));
342 }
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000343 addDep(depKey);
344 valuesMissing = true;
345 builder.put(depKey, ValueOrExceptionUtils.ofNull());
346 continue;
347 }
348
349 if (!directDeps.contains(depKey)) {
350 // If this child is done, we will return it, but also record that it was newly requested
351 // so that the dependency can be properly registered in the graph.
352 addDep(depKey);
353 }
354
355 replayingNestedSetEventVisitor.visit(value.getTransitiveEvents());
356 ErrorInfo errorInfo = value.getErrorInfo();
357
358 if (errorInfo != null) {
359 childErrorInfos.add(errorInfo);
360 }
361
362 if (value.getValue() != null && (keepGoing || errorInfo == null)) {
363 // If the dep did compute a value, it is given to the caller if we are in keepGoing mode
364 // or if we are in noKeepGoingMode and there were no errors computing it.
365 builder.put(depKey, ValueOrExceptionUtils.ofValueUntyped(value.getValue()));
366 continue;
367 }
368
369 // There was an error building the value, which we will either report by throwing an
370 // exception or insulate the caller from by returning null.
371 Preconditions.checkNotNull(errorInfo, "%s %s %s", skyKey, depKey, value);
372
373 if (!keepGoing && errorInfo.getException() != null && bubbleErrorInfo == null) {
374 // Child errors should not be propagated in noKeepGoing mode (except during error
375 // bubbling). Instead we should fail fast.
376
377 // We arbitrarily record the first child error.
378 if (depErrorKey == null) {
379 depErrorKey = depKey;
380 }
381 valuesMissing = true;
382 builder.put(depKey, ValueOrExceptionUtils.ofNull());
383 continue;
384 }
385
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100386 if (bubbleErrorInfo != null) {
Nathan Harmata267b0a12015-03-27 18:15:46 +0000387 // Set interrupted status, to try to prevent the calling SkyFunction from doing anything
388 // fancy after this. SkyFunctions executed during error bubbling are supposed to
389 // (quickly) rethrow errors or return a value/null (but there's currently no way to
390 // enforce this).
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000391 Thread.currentThread().interrupt();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100392 }
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000393 if (errorInfo.getException() != null) {
394 // Give builder a chance to handle this exception.
395 Exception e = errorInfo.getException();
396 builder.put(depKey, ValueOrExceptionUtils.ofExn(e));
397 continue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100398 }
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000399 // In a cycle.
400 Preconditions.checkState(!Iterables.isEmpty(errorInfo.getCycleInfo()), "%s %s %s %s",
401 skyKey, depKey, errorInfo, value);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100402 valuesMissing = true;
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000403 builder.put(depKey, ValueOrExceptionUtils.ofNull());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100404 }
Nathan Harmata6fc9fa02015-03-27 17:48:57 +0000405 return builder.build();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100406 }
407
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100408 @Override
409 public <E1 extends Exception, E2 extends Exception, E3 extends Exception,
410 E4 extends Exception> Map<SkyKey, ValueOrException4<E1, E2, E3, E4>> getValuesOrThrow(
411 Iterable<SkyKey> depKeys, Class<E1> exceptionClass1, Class<E2> exceptionClass2,
412 Class<E3> exceptionClass3, Class<E4> exceptionClass4) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100413 newlyRequestedDeps.startGroup();
Janak Ramakrishnan24f5e692015-04-10 19:39:55 +0000414 Map<SkyKey, ValueOrException4<E1, E2, E3, E4>> result = super.getValuesOrThrow(
415 depKeys, exceptionClass1, exceptionClass2, exceptionClass3, exceptionClass4);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100416 newlyRequestedDeps.endGroup();
Janak Ramakrishnan24f5e692015-04-10 19:39:55 +0000417 return result;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100418 }
419
420 private void addDep(SkyKey key) {
421 if (!newlyRequestedDeps.contains(key)) {
422 // dep may have been requested already this evaluation. If not, add it.
423 newlyRequestedDeps.add(key);
424 }
425 }
426
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100427 /**
428 * If {@code !keepGoing} and there is at least one dep in error, returns a dep in error.
429 * Otherwise returns {@code null}.
430 */
431 @Nullable
432 private SkyKey getDepErrorKey() {
433 return depErrorKey;
434 }
435
436 @Override
437 public EventHandler getListener() {
438 checkActive();
439 return eventHandler;
440 }
441
442 private void doneBuilding() {
443 building = false;
444 }
445
446 /**
447 * Apply the change to the graph (mostly) atomically and signal all nodes that are waiting for
448 * this node to complete. Adding nodes and signaling is not atomic, but may need to be changed
449 * for interruptibility.
450 *
451 * <p>Parents are only enqueued if {@code enqueueParents} holds. Parents should be enqueued
452 * unless (1) this node is being built after the main evaluation has aborted, or (2) this node
453 * is being built with --nokeep_going, and so we are about to shut down the main evaluation
454 * anyway.
455 *
456 * <p>The node entry is informed if the node's value and error are definitive via the flag
457 * {@code completeValue}.
458 */
459 void commit(boolean enqueueParents) {
460 NodeEntry primaryEntry = Preconditions.checkNotNull(graph.get(skyKey), skyKey);
461 // Construct the definitive error info, if there is one.
462 finalizeErrorInfo();
463
464 // We have the following implications:
465 // errorInfo == null => value != null => enqueueParents.
466 // All these implications are strict:
467 // (1) errorInfo != null && value != null happens for values with recoverable errors.
468 // (2) value == null && enqueueParents happens for values that are found to have errors
469 // during a --keep_going build.
470
471 NestedSet<TaggedEvents> events = buildEvents(/*missingChildren=*/false);
Janak Ramakrishnan08ec2572015-03-12 01:05:15 +0000472 Version valueVersion;
473 SkyValue valueWithMetadata;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100474 if (value == null) {
475 Preconditions.checkNotNull(errorInfo, "%s %s", skyKey, primaryEntry);
Janak Ramakrishnan08ec2572015-03-12 01:05:15 +0000476 valueWithMetadata = ValueWithMetadata.error(errorInfo, events);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100477 } else {
478 // We must be enqueueing parents if we have a value.
479 Preconditions.checkState(enqueueParents, "%s %s", skyKey, primaryEntry);
Janak Ramakrishnan08ec2572015-03-12 01:05:15 +0000480 valueWithMetadata = ValueWithMetadata.normal(value, errorInfo, events);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100481 }
Janak Ramakrishnan08ec2572015-03-12 01:05:15 +0000482 // If this entry is dirty, setValue may not actually change it, if it determines that
483 // the data being written now is the same as the data already present in the entry.
484 // We could consider using max(childVersions) here instead of graphVersion. When full
485 // versioning is implemented, this would allow evaluation at a version between
486 // max(childVersions) and graphVersion to re-use this result.
487 Set<SkyKey> reverseDeps = primaryEntry.setValue(valueWithMetadata, graphVersion);
488 // Note that if this update didn't actually change the value entry, this version may not
489 // be the graph version.
490 valueVersion = primaryEntry.getVersion();
491 Preconditions.checkState(valueVersion.atMost(graphVersion),
492 "%s should be at most %s in the version partial ordering",
493 valueVersion, graphVersion);
494 if (progressReceiver != null) {
495 // Tell the receiver that this value was built. If valueVersion.equals(graphVersion), it
496 // was evaluated this run, and so was changed. Otherwise, it is less than graphVersion,
497 // by the Preconditions check above, and was not actually changed this run -- when it was
498 // written above, its version stayed below this update's version, so its value remains the
499 // same as before.
Mark Schaller4752dbb2015-08-20 18:57:44 +0000500 progressReceiver.evaluated(skyKey, Suppliers.ofInstance(value),
Janak Ramakrishnan08ec2572015-03-12 01:05:15 +0000501 valueVersion.equals(graphVersion) ? EvaluationState.BUILT : EvaluationState.CLEAN);
502 }
503 signalValuesAndEnqueueIfReady(enqueueParents ? visitor : null, reverseDeps, valueVersion);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100504
505 visitor.notifyDone(skyKey);
506 replayingNestedSetEventVisitor.visit(events);
507 }
508
509 @Nullable
510 private String getTagFromKey() {
511 return skyFunctions.get(skyKey.functionName()).extractTag(skyKey);
512 }
513
514 /**
515 * Gets the latch that is counted down when an exception is thrown in {@code
516 * AbstractQueueVisitor}. For use in tests to check if an exception actually was thrown. Calling
517 * {@code AbstractQueueVisitor#awaitExceptionForTestingOnly} can throw a spurious {@link
518 * InterruptedException} because {@link CountDownLatch#await} checks the interrupted bit before
519 * returning, even if the latch is already at 0. See bug "testTwoErrors is flaky".
520 */
521 CountDownLatch getExceptionLatchForTesting() {
522 return visitor.getExceptionLatchForTestingOnly();
523 }
524
525 @Override
526 public boolean inErrorBubblingForTesting() {
527 return bubbleErrorInfo != null;
528 }
529 }
530
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100531 private class ValueVisitor extends AbstractQueueVisitor {
532 private AtomicBoolean preventNewEvaluations = new AtomicBoolean(false);
533 private final Set<SkyKey> inflightNodes = Sets.newConcurrentHashSet();
534
535 private ValueVisitor(int threadCount) {
536 super(/*concurrent*/true,
537 threadCount,
538 threadCount,
539 1, TimeUnit.SECONDS,
540 /*failFastOnException*/true,
541 /*failFastOnInterrupt*/true,
542 "skyframe-evaluator");
543 }
544
545 @Override
546 protected boolean isCriticalError(Throwable e) {
547 return e instanceof RuntimeException;
548 }
549
550 protected void waitForCompletion() throws InterruptedException {
551 work(/*failFastOnInterrupt=*/true);
552 }
553
554 public void enqueueEvaluation(final SkyKey key) {
555 // We unconditionally add the key to the set of in-flight nodes because even if evaluation is
556 // never scheduled we still want to remove the previously created NodeEntry from the graph.
557 // Otherwise we would leave the graph in a weird state (wasteful garbage in the best case and
558 // inconsistent in the worst case).
559 boolean newlyEnqueued = inflightNodes.add(key);
560 // All nodes enqueued for evaluation will be either verified clean, re-evaluated, or cleaned
561 // up after being in-flight when an error happens in nokeep_going mode or in the event of an
562 // interrupt. In any of these cases, they won't be dirty anymore.
563 if (newlyEnqueued) {
564 dirtyKeyTracker.notDirty(key);
565 }
566 if (preventNewEvaluations.get()) {
567 return;
568 }
569 if (newlyEnqueued && progressReceiver != null) {
570 progressReceiver.enqueueing(key);
571 }
572 enqueue(new Evaluate(this, key));
573 }
574
Janak Ramakrishnanffa75fe2015-05-01 03:33:58 +0000575 /**
576 * Stop any new evaluations from being enqueued. Returns whether this was the first thread to
577 * request a halt. If true, this thread should proceed to throw an exception. If false, another
578 * thread already requested a halt and will throw an exception, and so this thread can simply
579 * end.
580 */
581 boolean preventNewEvaluations() {
582 return preventNewEvaluations.compareAndSet(false, true);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100583 }
584
Janak Ramakrishnanffa75fe2015-05-01 03:33:58 +0000585 void notifyDone(SkyKey key) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100586 inflightNodes.remove(key);
587 }
588
589 private boolean isInflight(SkyKey key) {
590 return inflightNodes.contains(key);
591 }
592 }
593
594 /**
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000595 * If the entry is dirty and not already rebuilding, puts it in a state that it can rebuild, and
596 * removes it as a reverse dep from any dirty direct deps it had yet to check.
597 */
598 private void maybeMarkRebuildingAndRemoveRemainingDirtyDirectDeps(SkyKey key, NodeEntry entry) {
599 if (entry.isDirty() && entry.getDirtyState() != DirtyState.REBUILDING) {
600 Collection<SkyKey> depsToRemove = entry.markRebuildingAndGetAllRemainingDirtyDirectDeps();
601 Map<SkyKey, NodeEntry> depsToClearFrom = graph.getBatch(depsToRemove);
Janak Ramakrishnanc2c123e2015-09-30 18:23:03 +0000602 if (depsToClearFrom.size() != depsToRemove.size()) {
603 throw new IllegalStateException(
604 "At least one dep of a dirty node wasn't present in the graph: "
605 + Sets.difference(ImmutableSet.copyOf(depsToRemove), depsToClearFrom.keySet())
606 + " for "
607 + key
608 + " with entry "
609 + entry
610 + ". Sizes: "
611 + depsToRemove.size()
612 + ", "
613 + depsToClearFrom.size());
614 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000615 for (NodeEntry depEntry : depsToClearFrom.values()) {
616 depEntry.removeReverseDep(key);
617 }
618 }
619 }
620
621 private enum DirtyOutcome {
622 ALREADY_PROCESSED,
623 NEEDS_EVALUATION
624 }
625
Janak Ramakrishnande281a72015-09-24 21:12:05 +0000626 // Take advantage of graphs that cache batch requests and prefetch keys that will be needed.
627 private static void batchPrefetchAndAssertDone(
628 Set<SkyKey> keys, QueryableGraph graph, SkyKey keyForDebugging) {
629 Map<SkyKey, NodeEntry> batchMap = graph.getBatch(keys);
630 if (batchMap.size() != keys.size()) {
631 throw new IllegalStateException(
632 "Missing keys for " + keyForDebugging + ": " + Sets.difference(keys, batchMap.keySet()));
633 }
634 for (Map.Entry<SkyKey, NodeEntry> entry : batchMap.entrySet()) {
635 Preconditions.checkState(
636 entry.getValue().isDone(), "%s had not done %s", keyForDebugging, entry);
637 }
638 }
639
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000640 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100641 * An action that evaluates a value.
642 */
643 private class Evaluate implements Runnable {
644 private final ValueVisitor visitor;
645 /** The name of the value to be evaluated. */
646 private final SkyKey skyKey;
647
648 private Evaluate(ValueVisitor visitor, SkyKey skyKey) {
649 this.visitor = visitor;
650 this.skyKey = skyKey;
651 }
652
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000653 private void enqueueChild(SkyKey skyKey, NodeEntry entry, SkyKey child, boolean dirtyParent) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100654 Preconditions.checkState(!entry.isDone(), "%s %s", skyKey, entry);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100655
656 NodeEntry depEntry = graph.createIfAbsent(child);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000657 DependencyState dependencyState =
658 dirtyParent
659 ? depEntry.checkIfDoneForDirtyReverseDep(skyKey)
660 : depEntry.addReverseDepAndCheckIfDone(skyKey);
661 switch (dependencyState) {
662 case DONE:
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100663 if (entry.signalDep(depEntry.getVersion())) {
664 // This can only happen if there are no more children to be added.
665 visitor.enqueueEvaluation(skyKey);
666 }
667 break;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000668 case ALREADY_EVALUATING:
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100669 break;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000670 case NEEDS_SCHEDULING:
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100671 visitor.enqueueEvaluation(child);
672 break;
673 }
674 }
675
676 /**
677 * Returns true if this depGroup consists of the error transience value and the error transience
678 * value is newer than the entry, meaning that the entry must be re-evaluated.
679 */
680 private boolean invalidatedByErrorTransience(Collection<SkyKey> depGroup, NodeEntry entry) {
681 return depGroup.size() == 1
682 && depGroup.contains(ErrorTransienceValue.key())
683 && !graph.get(ErrorTransienceValue.key()).getVersion().atMost(entry.getVersion());
684 }
685
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000686 private DirtyOutcome maybeHandleDirtyNode(NodeEntry state) {
687 if (!state.isDirty()) {
688 return DirtyOutcome.NEEDS_EVALUATION;
689 }
690 switch (state.getDirtyState()) {
691 case CHECK_DEPENDENCIES:
692 // Evaluating a dirty node for the first time, and checking its children to see if any
693 // of them have changed. Note that there must be dirty children for this to happen.
694
695 // Check the children group by group -- we don't want to evaluate a value that is no
696 // longer needed because an earlier dependency changed. For example, //foo:foo depends
697 // on target //bar:bar and is built. Then foo/BUILD is modified to remove the dependence
698 // on bar, and bar/BUILD is deleted. Reloading //bar:bar would incorrectly throw an
699 // exception. To avoid this, we must reload foo/BUILD first, at which point we will
700 // discover that it has changed, and re-evaluate target //foo:foo from scratch.
701 // On the other hand, when an action requests all of its inputs, we can safely check all
702 // of them in parallel on a subsequent build. So we allow checking an entire group in
703 // parallel here, if the node builder requested a group last build.
704 // Note: every dep returned here must either have this node re-registered for it (using
705 // checkIfDoneForDirtyReverseDep) and be registered as a direct dep of this node, or have
706 // its reverse dep on this node removed. Failing to do either one of these would result in
707 // a graph inconsistency, where the child had a reverse dep on this node, but this node
708 // had no kind of dependency on the child.
709 Collection<SkyKey> directDepsToCheck = state.getNextDirtyDirectDeps();
710
711 if (invalidatedByErrorTransience(directDepsToCheck, state)) {
712 // If this dep is the ErrorTransienceValue and the ErrorTransienceValue has been
713 // updated then we need to force a rebuild. We would like to just signal the entry as
714 // usual, but we can't, because then the ErrorTransienceValue would remain as a dep,
715 // which would be incorrect if, for instance, the value re-evaluated to a non-error.
716 state.forceRebuild();
717 graph.get(ErrorTransienceValue.key()).removeReverseDep(skyKey);
718 return DirtyOutcome.NEEDS_EVALUATION;
719 }
720 if (!keepGoing) {
721 // This check ensures that we maintain the invariant that if a node with an error is
722 // reached during a no-keep-going build, none of its currently building parents
723 // finishes building. If the child isn't done building yet, it will detect on its own
724 // that it has an error (see the VERIFIED_CLEAN case below). On the other hand, if it
725 // is done, then it is the parent's responsibility to notice that, which we do here.
726 // We check the deps for errors so that we don't continue building this node if it has
727 // a child error.
728 Map<SkyKey, NodeEntry> entriesToCheck = graph.getBatch(directDepsToCheck);
729 for (Map.Entry<SkyKey, NodeEntry> entry : entriesToCheck.entrySet()) {
730 if (entry.getValue().isDone() && entry.getValue().getErrorInfo() != null) {
731 // If any child has an error, we arbitrarily add a dep on the first one (needed
732 // for error bubbling) and throw an exception coming from it.
733 SkyKey errorKey = entry.getKey();
734 NodeEntry errorEntry = entry.getValue();
735 state.addTemporaryDirectDeps(GroupedListHelper.create(ImmutableList.of(errorKey)));
736 errorEntry.checkIfDoneForDirtyReverseDep(skyKey);
737 // Perform the necessary bookkeeping for any deps that are not being used.
738 for (Map.Entry<SkyKey, NodeEntry> depEntry : entriesToCheck.entrySet()) {
739 if (!depEntry.getKey().equals(errorKey)) {
740 depEntry.getValue().removeReverseDep(skyKey);
741 }
742 }
743 if (!visitor.preventNewEvaluations()) {
744 // An error was already thrown in the evaluator. Don't do anything here.
745 return DirtyOutcome.ALREADY_PROCESSED;
746 }
747 throw SchedulerException.ofError(errorEntry.getErrorInfo(), entry.getKey());
748 }
749 }
750 }
751 // It is safe to add these deps back to the node -- even if one of them has changed, the
752 // contract of pruning is that the node will request these deps again when it rebuilds.
753 // We must add these deps before enqueuing them, so that the node knows that it depends
754 // on them. If one of these deps is the error transience node, the check we did above
755 // in #invalidatedByErrorTransience means that the error transience node is not newer
756 // than this node, so we are going to mark it clean (since the error transience node is
757 // always the last dep).
758 state.addTemporaryDirectDeps(GroupedListHelper.create(directDepsToCheck));
759 for (SkyKey directDep : directDepsToCheck) {
760 enqueueChild(skyKey, state, directDep, /*dirtyParent=*/ true);
761 }
762 return DirtyOutcome.ALREADY_PROCESSED;
763 case VERIFIED_CLEAN:
764 // No child has a changed value. This node can be marked done and its parents signaled
765 // without any re-evaluation.
766 visitor.notifyDone(skyKey);
767 Set<SkyKey> reverseDeps = state.markClean();
768 if (progressReceiver != null) {
769 // Tell the receiver that the value was not actually changed this run.
770 progressReceiver.evaluated(skyKey, new SkyValueSupplier(state), EvaluationState.CLEAN);
771 }
772 if (!keepGoing && state.getErrorInfo() != null) {
773 if (!visitor.preventNewEvaluations()) {
774 return DirtyOutcome.ALREADY_PROCESSED;
775 }
776 throw SchedulerException.ofError(state.getErrorInfo(), skyKey);
777 }
778 signalValuesAndEnqueueIfReady(visitor, reverseDeps, state.getVersion());
779 return DirtyOutcome.ALREADY_PROCESSED;
780 case NEEDS_REBUILDING:
781 maybeMarkRebuildingAndRemoveRemainingDirtyDirectDeps(skyKey, state);
782 // Fall through to REBUILDING case.
783 case REBUILDING:
784 return DirtyOutcome.NEEDS_EVALUATION;
785 default:
786 throw new IllegalStateException("key: " + skyKey + ", entry: " + state);
787 }
788 }
789
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100790 @Override
791 public void run() {
Janak Ramakrishnan62ae0cc2015-08-20 19:55:28 +0000792 NodeEntry state = Preconditions.checkNotNull(graph.get(skyKey), skyKey);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100793 Preconditions.checkState(state.isReady(), "%s %s", skyKey, state);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000794 if (maybeHandleDirtyNode(state) == DirtyOutcome.ALREADY_PROCESSED) {
795 return;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100796 }
797
798 // TODO(bazel-team): Once deps are requested in a deterministic order within a group, or the
799 // framework is resilient to rearranging group order, change this so that
800 // SkyFunctionEnvironment "follows along" as the node builder runs, iterating through the
801 // direct deps that were requested on a previous run. This would allow us to avoid the
802 // conversion of the direct deps into a set.
803 Set<SkyKey> directDeps = state.getTemporaryDirectDeps();
804 Preconditions.checkState(!directDeps.contains(ErrorTransienceValue.key()),
805 "%s cannot have a dep on ErrorTransienceValue during building: %s", skyKey, state);
Janak Ramakrishnande281a72015-09-24 21:12:05 +0000806 batchPrefetchAndAssertDone(directDeps, graph, skyKey);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100807 // Get the corresponding SkyFunction and call it on this value.
808 SkyFunctionEnvironment env = new SkyFunctionEnvironment(skyKey, directDeps, visitor);
809 SkyFunctionName functionName = skyKey.functionName();
810 SkyFunction factory = skyFunctions.get(functionName);
811 Preconditions.checkState(factory != null, "%s %s", functionName, state);
812
813 SkyValue value = null;
Nathan Harmata6f094bb2015-09-03 19:27:38 +0000814 long startTime = Profiler.nanoTimeMaybe();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100815 try {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100816 value = factory.compute(skyKey, env);
817 } catch (final SkyFunctionException builderException) {
818 ReifiedSkyFunctionException reifiedBuilderException =
819 new ReifiedSkyFunctionException(builderException, skyKey);
820 // Propagated transitive errors are treated the same as missing deps.
821 if (reifiedBuilderException.getRootCauseSkyKey().equals(skyKey)) {
822 boolean shouldFailFast = !keepGoing || builderException.isCatastrophic();
823 if (shouldFailFast) {
824 // After we commit this error to the graph but before the eval call completes with the
825 // error there is a race-like opportunity for the error to be used, either by an
826 // in-flight computation or by a future computation.
Janak Ramakrishnanffa75fe2015-05-01 03:33:58 +0000827 if (!visitor.preventNewEvaluations()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100828 // This is not the first error encountered, so we ignore it so that we can terminate
829 // with the first error.
830 return;
831 }
832 }
833
834 registerNewlyDiscoveredDepsForDoneEntry(skyKey, state, env);
Michajlo Matijkiwaa058282015-09-28 22:13:27 +0000835 ErrorInfo errorInfo = ErrorInfo.fromException(reifiedBuilderException);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100836 env.setError(errorInfo);
837 env.commit(/*enqueueParents=*/keepGoing);
838 if (!shouldFailFast) {
839 return;
840 }
841 throw SchedulerException.ofError(errorInfo, skyKey);
842 }
843 } catch (InterruptedException ie) {
844 // InterruptedException cannot be thrown by Runnable.run, so we must wrap it.
845 // Interrupts can be caught by both the Evaluator and the AbstractQueueVisitor.
846 // The former will unwrap the IE and propagate it as is; the latter will throw a new IE.
847 throw SchedulerException.ofInterruption(ie, skyKey);
848 } catch (RuntimeException re) {
849 // Programmer error (most likely NPE or a failed precondition in a SkyFunction). Output
850 // some context together with the exception.
851 String msg = prepareCrashMessage(skyKey, state.getInProgressReverseDeps());
852 throw new RuntimeException(msg, re);
853 } finally {
854 env.doneBuilding();
Nathan Harmata6f094bb2015-09-03 19:27:38 +0000855 long elapsedTimeNanos = Profiler.nanoTimeMaybe() - startTime;
856 if (elapsedTimeNanos > 0) {
857 if (progressReceiver != null) {
858 progressReceiver.computed(skyKey, elapsedTimeNanos);
859 }
860 Profiler.instance().logSimpleTaskDuration(startTime, elapsedTimeNanos,
861 ProfilerTask.SKYFUNCTION, skyKey);
862 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100863 }
864
865 GroupedListHelper<SkyKey> newDirectDeps = env.newlyRequestedDeps;
866
867 if (value != null) {
Janak Ramakrishnan24f5e692015-04-10 19:39:55 +0000868 Preconditions.checkState(!env.valuesMissing(),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100869 "%s -> %s, ValueEntry: %s", skyKey, newDirectDeps, state);
870 env.setValue(value);
871 registerNewlyDiscoveredDepsForDoneEntry(skyKey, state, env);
872 env.commit(/*enqueueParents=*/true);
873 return;
874 }
875
Janak Ramakrishnanffa75fe2015-05-01 03:33:58 +0000876 if (env.getDepErrorKey() != null) {
877 Preconditions.checkState(!keepGoing, "%s %s %s", skyKey, state, env.getDepErrorKey());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100878 // We encountered a child error in noKeepGoing mode, so we want to fail fast. But we first
879 // need to add the edge between the current node and the child error it requested so that
880 // error bubbling can occur. Note that this edge will subsequently be removed during graph
881 // cleaning (since the current node will never be committed to the graph).
882 SkyKey childErrorKey = env.getDepErrorKey();
883 NodeEntry childErrorEntry = Preconditions.checkNotNull(graph.get(childErrorKey),
884 "skyKey: %s, state: %s childErrorKey: %s", skyKey, state, childErrorKey);
885 if (!state.getTemporaryDirectDeps().contains(childErrorKey)) {
886 // This means the cached error was freshly requested (e.g. the parent has never been
887 // built before).
888 Preconditions.checkState(newDirectDeps.contains(childErrorKey), "%s %s %s", state,
889 childErrorKey, newDirectDeps);
890 state.addTemporaryDirectDeps(GroupedListHelper.create(ImmutableList.of(childErrorKey)));
891 DependencyState childErrorState = childErrorEntry.addReverseDepAndCheckIfDone(skyKey);
892 Preconditions.checkState(childErrorState == DependencyState.DONE,
893 "skyKey: %s, state: %s childErrorKey: %s", skyKey, state, childErrorKey,
894 childErrorEntry);
895 } else {
896 // This means the cached error was previously requested, and was then subsequently (after
897 // a restart) requested along with another sibling dep. This can happen on an incremental
898 // eval call when the parent is dirty and the child error is in a separate dependency
899 // group from the sibling dep.
900 Preconditions.checkState(!newDirectDeps.contains(childErrorKey), "%s %s %s", state,
901 childErrorKey, newDirectDeps);
902 Preconditions.checkState(childErrorEntry.isDone(),
903 "skyKey: %s, state: %s childErrorKey: %s", skyKey, state, childErrorKey,
904 childErrorEntry);
905 }
906 ErrorInfo childErrorInfo = Preconditions.checkNotNull(childErrorEntry.getErrorInfo());
907 throw SchedulerException.ofError(childErrorInfo, childErrorKey);
908 }
909
910 // TODO(bazel-team): This code is not safe to interrupt, because we would lose the state in
911 // newDirectDeps.
912
913 // TODO(bazel-team): An ill-behaved SkyFunction can throw us into an infinite loop where we
914 // add more dependencies on every run. [skyframe-core]
915
916 // Add all new keys to the set of known deps.
917 state.addTemporaryDirectDeps(newDirectDeps);
918
919 // If there were no newly requested dependencies, at least one of them was in error or there
920 // is a bug in the SkyFunction implementation. The environment has collected its errors, so we
921 // just order it to be built.
922 if (newDirectDeps.isEmpty()) {
923 // TODO(bazel-team): This means a bug in the SkyFunction. What to do?
924 Preconditions.checkState(!env.childErrorInfos.isEmpty(), "%s %s", skyKey, state);
925 env.commit(/*enqueueParents=*/keepGoing);
926 if (!keepGoing) {
927 throw SchedulerException.ofError(state.getErrorInfo(), skyKey);
928 }
929 return;
930 }
931
932 for (SkyKey newDirectDep : newDirectDeps) {
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000933 enqueueChild(skyKey, state, newDirectDep, /*dirtyParent=*/ false);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100934 }
935 // It is critical that there is no code below this point.
936 }
937
938 private String prepareCrashMessage(SkyKey skyKey, Iterable<SkyKey> reverseDeps) {
939 StringBuilder reverseDepDump = new StringBuilder();
940 for (SkyKey key : reverseDeps) {
941 if (reverseDepDump.length() > MAX_REVERSEDEP_DUMP_LENGTH) {
942 reverseDepDump.append(", ...");
943 break;
944 }
945 if (reverseDepDump.length() > 0) {
946 reverseDepDump.append(", ");
947 }
948 reverseDepDump.append("'");
949 reverseDepDump.append(key.toString());
950 reverseDepDump.append("'");
951 }
952
953 return String.format(
954 "Unrecoverable error while evaluating node '%s' (requested by nodes %s)",
955 skyKey, reverseDepDump);
956 }
957
958 private static final int MAX_REVERSEDEP_DUMP_LENGTH = 1000;
959 }
960
961 /**
962 * Signals all parents that this node is finished. If visitor is not null, also enqueues any
963 * parents that are ready. If visitor is null, indicating that we are building this node after
964 * the main build aborted, then skip any parents that are already done (that can happen with
965 * cycles).
966 */
967 private void signalValuesAndEnqueueIfReady(@Nullable ValueVisitor visitor, Iterable<SkyKey> keys,
968 Version version) {
969 if (visitor != null) {
970 for (SkyKey key : keys) {
971 if (graph.get(key).signalDep(version)) {
972 visitor.enqueueEvaluation(key);
973 }
974 }
975 } else {
976 for (SkyKey key : keys) {
977 NodeEntry entry = Preconditions.checkNotNull(graph.get(key), key);
978 if (!entry.isDone()) {
979 // In cycles, we can have parents that are already done.
980 entry.signalDep(version);
981 }
982 }
983 }
984 }
985
986 /**
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000987 * If child is not done, removes {@param inProgressParent} from {@param child}'s reverse deps.
988 * Returns whether child should be removed from inProgressParent's entry's direct deps.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100989 */
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000990 private boolean removeIncompleteChild(SkyKey inProgressParent, SkyKey child) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100991 NodeEntry childEntry = graph.get(child);
992 if (!isDoneForBuild(childEntry)) {
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000993 childEntry.removeInProgressReverseDep(inProgressParent);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100994 return true;
995 }
996 return false;
997 }
998
999 /**
1000 * Add any additional deps that were registered during the run of a builder that finished by
1001 * creating a node or throwing an error. Builders may throw errors even if all their deps were
1002 * not provided -- we trust that a SkyFunction may be know it should throw an error even if not
1003 * all of its requested deps are done. However, that means we're assuming the SkyFunction would
1004 * throw that same error if all of its requested deps were done. Unfortunately, there is no way to
1005 * enforce that condition.
1006 */
1007 private void registerNewlyDiscoveredDepsForDoneEntry(SkyKey skyKey, NodeEntry entry,
1008 SkyFunctionEnvironment env) {
1009 Set<SkyKey> unfinishedDeps = new HashSet<>();
1010 Iterables.addAll(unfinishedDeps,
1011 Iterables.filter(env.newlyRequestedDeps, Predicates.not(nodeEntryIsDone)));
1012 env.newlyRequestedDeps.remove(unfinishedDeps);
1013 entry.addTemporaryDirectDeps(env.newlyRequestedDeps);
1014 for (SkyKey newDep : env.newlyRequestedDeps) {
1015 NodeEntry depEntry = graph.get(newDep);
1016 DependencyState triState = depEntry.addReverseDepAndCheckIfDone(skyKey);
1017 Preconditions.checkState(DependencyState.DONE == triState,
1018 "new dep %s was not already done for %s. ValueEntry: %s. DepValueEntry: %s",
1019 newDep, skyKey, entry, depEntry);
1020 entry.signalDep();
1021 }
1022 Preconditions.checkState(entry.isReady(), "%s %s %s", skyKey, entry, env.newlyRequestedDeps);
1023 }
1024
1025 private void informProgressReceiverThatValueIsDone(SkyKey key) {
1026 if (progressReceiver != null) {
1027 NodeEntry entry = graph.get(key);
1028 Preconditions.checkState(entry.isDone(), entry);
1029 SkyValue value = entry.getValue();
Janak Ramakrishnan08ec2572015-03-12 01:05:15 +00001030 Version valueVersion = entry.getVersion();
1031 Preconditions.checkState(valueVersion.atMost(graphVersion),
1032 "%s should be at most %s in the version partial ordering", valueVersion, graphVersion);
1033 // For most nodes we do not inform the progress receiver if they were already done when we
1034 // retrieve them, but top-level nodes are presumably of more interest.
1035 // If valueVersion is not equal to graphVersion, it must be less than it (by the
1036 // Preconditions check above), and so the node is clean.
Mark Schaller4752dbb2015-08-20 18:57:44 +00001037 progressReceiver.evaluated(key, Suppliers.ofInstance(value), valueVersion.equals(graphVersion)
Janak Ramakrishnan08ec2572015-03-12 01:05:15 +00001038 ? EvaluationState.BUILT
1039 : EvaluationState.CLEAN);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001040 }
1041 }
1042
1043 @Override
1044 @ThreadCompatible
1045 public <T extends SkyValue> EvaluationResult<T> eval(Iterable<SkyKey> skyKeys)
1046 throws InterruptedException {
1047 ImmutableSet<SkyKey> skyKeySet = ImmutableSet.copyOf(skyKeys);
1048
1049 // Optimization: if all required node values are already present in the cache, return them
1050 // directly without launching the heavy machinery, spawning threads, etc.
1051 // Inform progressReceiver that these nodes are done to be consistent with the main code path.
1052 if (Iterables.all(skyKeySet, nodeEntryIsDone)) {
1053 for (SkyKey skyKey : skyKeySet) {
1054 informProgressReceiverThatValueIsDone(skyKey);
1055 }
1056 // Note that the 'catastrophe' parameter doesn't really matter here (it's only used for
1057 // sanity checking).
1058 return constructResult(null, skyKeySet, null, /*catastrophe=*/false);
1059 }
1060
1061 if (!keepGoing) {
1062 Set<SkyKey> cachedErrorKeys = new HashSet<>();
1063 for (SkyKey skyKey : skyKeySet) {
1064 NodeEntry entry = graph.get(skyKey);
1065 if (entry == null) {
1066 continue;
1067 }
1068 if (entry.isDone() && entry.getErrorInfo() != null) {
1069 informProgressReceiverThatValueIsDone(skyKey);
1070 cachedErrorKeys.add(skyKey);
1071 }
1072 }
1073
1074 // Errors, even cached ones, should halt evaluations not in keepGoing mode.
1075 if (!cachedErrorKeys.isEmpty()) {
1076 // Note that the 'catastrophe' parameter doesn't really matter here (it's only used for
1077 // sanity checking).
1078 return constructResult(null, cachedErrorKeys, null, /*catastrophe=*/false);
1079 }
1080 }
1081
1082 // We delay this check until we know that some kind of evaluation is necessary, since !keepGoing
1083 // and !keepsEdges are incompatible only in the case of a failed evaluation -- there is no
1084 // need to be overly harsh to callers who are just trying to retrieve a cached result.
1085 Preconditions.checkState(keepGoing || !(graph instanceof InMemoryGraph)
1086 || ((InMemoryGraph) graph).keepsEdges(),
1087 "nokeep_going evaluations are not allowed if graph edges are not kept: %s", skyKeys);
1088
1089 Profiler.instance().startTask(ProfilerTask.SKYFRAME_EVAL, skyKeySet);
1090 try {
1091 return eval(skyKeySet, new ValueVisitor(threadCount));
1092 } finally {
1093 Profiler.instance().completeTask(ProfilerTask.SKYFRAME_EVAL);
1094 }
1095 }
1096
1097 @ThreadCompatible
1098 private <T extends SkyValue> EvaluationResult<T> eval(ImmutableSet<SkyKey> skyKeys,
1099 ValueVisitor visitor) throws InterruptedException {
1100 // We unconditionally add the ErrorTransienceValue here, to ensure that it will be created, and
1101 // in the graph, by the time that it is needed. Creating it on demand in a parallel context sets
1102 // up a race condition, because there is no way to atomically create a node and set its value.
1103 NodeEntry errorTransienceEntry = graph.createIfAbsent(ErrorTransienceValue.key());
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001104 if (!errorTransienceEntry.isDone()) {
1105 injectValue(
1106 ErrorTransienceValue.key(),
1107 new ErrorTransienceValue(),
1108 graphVersion,
1109 graph,
1110 dirtyKeyTracker);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001111 }
1112 for (SkyKey skyKey : skyKeys) {
1113 NodeEntry entry = graph.createIfAbsent(skyKey);
1114 // This must be equivalent to the code in enqueueChild above, in order to be thread-safe.
1115 switch (entry.addReverseDepAndCheckIfDone(null)) {
1116 case NEEDS_SCHEDULING:
1117 visitor.enqueueEvaluation(skyKey);
1118 break;
1119 case DONE:
1120 informProgressReceiverThatValueIsDone(skyKey);
1121 break;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001122 case ALREADY_EVALUATING:
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001123 break;
1124 default:
1125 throw new IllegalStateException(entry + " for " + skyKey + " in unknown state");
1126 }
1127 }
1128 try {
Janak Ramakrishnan54111292015-09-09 21:44:07 +00001129 return waitForCompletionAndConstructResult(visitor, skyKeys);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001130 } finally {
Janak Ramakrishnan54111292015-09-09 21:44:07 +00001131 inflightKeysReceiver.accept(visitor.inflightNodes);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001132 }
1133 }
1134
1135 private <T extends SkyValue> EvaluationResult<T> waitForCompletionAndConstructResult(
1136 ValueVisitor visitor, Iterable<SkyKey> skyKeys) throws InterruptedException {
1137 Map<SkyKey, ValueWithMetadata> bubbleErrorInfo = null;
1138 boolean catastrophe = false;
1139 try {
1140 visitor.waitForCompletion();
1141 } catch (final SchedulerException e) {
1142 Throwables.propagateIfPossible(e.getCause(), InterruptedException.class);
1143 if (Thread.interrupted()) {
1144 // As per the contract of AbstractQueueVisitor#work, if an unchecked exception is thrown and
1145 // the build is interrupted, the thrown exception is what will be rethrown. Since the user
1146 // presumably wanted to interrupt the build, we ignore the thrown SchedulerException (which
1147 // doesn't indicate a programming bug) and throw an InterruptedException.
1148 throw new InterruptedException();
1149 }
1150
1151 SkyKey errorKey = Preconditions.checkNotNull(e.getFailedValue(), e);
1152 // ErrorInfo could only be null if SchedulerException wrapped an InterruptedException, but
1153 // that should have been propagated.
1154 ErrorInfo errorInfo = Preconditions.checkNotNull(e.getErrorInfo(), errorKey);
1155 catastrophe = errorInfo.isCatastrophic();
1156 if (!catastrophe || !keepGoing) {
1157 bubbleErrorInfo = bubbleErrorUp(errorInfo, errorKey, skyKeys, visitor);
1158 } else {
1159 // Bubbling the error up requires that graph edges are present for done nodes. This is not
1160 // always the case in a keepGoing evaluation, since it is assumed that done nodes do not
1161 // need to be traversed. In this case, we hope the caller is tolerant of a possibly empty
1162 // result, and return prematurely.
Janak Ramakrishnan0afd4532015-08-24 17:27:48 +00001163 bubbleErrorInfo =
1164 ImmutableMap.of(
1165 errorKey,
1166 ValueWithMetadata.wrapWithMetadata(
1167 graph.get(errorKey).getValueMaybeWithMetadata()));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001168 }
1169 }
1170
1171 // Successful evaluation, either because keepGoing or because we actually did succeed.
1172 // TODO(bazel-team): Maybe report root causes during the build for lower latency.
1173 return constructResult(visitor, skyKeys, bubbleErrorInfo, catastrophe);
1174 }
1175
1176 /**
1177 * Walk up graph to find a top-level node (without parents) that wanted this failure. Store
1178 * the failed nodes along the way in a map, with ErrorInfos that are appropriate for that layer.
1179 * Example:
1180 * foo bar
1181 * \ /
1182 * unrequested baz
1183 * \ |
1184 * failed-node
1185 * User requests foo, bar. When failed-node fails, we look at its parents. unrequested is not
1186 * in-flight, so we replace failed-node by baz and repeat. We look at baz's parents. foo is
1187 * in-flight, so we replace baz by foo. Since foo is a top-level node and doesn't have parents,
1188 * we then break, since we know a top-level node, foo, that depended on the failed node.
1189 *
1190 * There's the potential for a weird "track jump" here in the case:
1191 * foo
1192 * / \
1193 * fail1 fail2
1194 * If fail1 and fail2 fail simultaneously, fail2 may start propagating up in the loop below.
1195 * However, foo requests fail1 first, and then throws an exception based on that. This is not
1196 * incorrect, but may be unexpected.
1197 *
1198 * <p>Returns a map of errors that have been constructed during the bubbling up, so that the
1199 * appropriate error can be returned to the caller, even though that error was not written to the
1200 * graph. If a cycle is detected during the bubbling, this method aborts and returns null so that
1201 * the normal cycle detection can handle the cycle.
1202 *
1203 * <p>Note that we are not propagating error to the first top-level node but to the highest one,
1204 * because during this process we can add useful information about error from other nodes.
1205 */
1206 private Map<SkyKey, ValueWithMetadata> bubbleErrorUp(final ErrorInfo leafFailure,
1207 SkyKey errorKey, Iterable<SkyKey> skyKeys, ValueVisitor visitor) {
1208 Set<SkyKey> rootValues = ImmutableSet.copyOf(skyKeys);
1209 ErrorInfo error = leafFailure;
1210 Map<SkyKey, ValueWithMetadata> bubbleErrorInfo = new HashMap<>();
1211 boolean externalInterrupt = false;
1212 while (true) {
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001213 NodeEntry errorEntry = Preconditions.checkNotNull(graph.get(errorKey), errorKey);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001214 Iterable<SkyKey> reverseDeps = errorEntry.isDone()
1215 ? errorEntry.getReverseDeps()
1216 : errorEntry.getInProgressReverseDeps();
1217 // We should break from loop only when node doesn't have any parents.
1218 if (Iterables.isEmpty(reverseDeps)) {
1219 Preconditions.checkState(rootValues.contains(errorKey),
1220 "Current key %s has to be a top-level key: %s", errorKey, rootValues);
1221 break;
1222 }
1223 SkyKey parent = null;
1224 NodeEntry parentEntry = null;
1225 for (SkyKey bubbleParent : reverseDeps) {
1226 if (bubbleErrorInfo.containsKey(bubbleParent)) {
1227 // We are in a cycle. Don't try to bubble anything up -- cycle detection will kick in.
1228 return null;
1229 }
1230 NodeEntry bubbleParentEntry = Preconditions.checkNotNull(graph.get(bubbleParent),
1231 "parent %s of %s not in graph", bubbleParent, errorKey);
1232 // Might be the parent that requested the error.
1233 if (bubbleParentEntry.isDone()) {
1234 // This parent is cached from a previous evaluate call. We shouldn't bubble up to it
1235 // since any error message produced won't be meaningful to this evaluate call.
1236 // The child error must also be cached from a previous build.
1237 Preconditions.checkState(errorEntry.isDone(), "%s %s", errorEntry, bubbleParentEntry);
1238 Version parentVersion = bubbleParentEntry.getVersion();
1239 Version childVersion = errorEntry.getVersion();
1240 Preconditions.checkState(childVersion.atMost(graphVersion)
1241 && !childVersion.equals(graphVersion),
1242 "child entry is not older than the current graph version, but had a done parent. "
1243 + "child: %s childEntry: %s, childVersion: %s"
1244 + "bubbleParent: %s bubbleParentEntry: %s, parentVersion: %s, graphVersion: %s",
1245 errorKey, errorEntry, childVersion,
1246 bubbleParent, bubbleParentEntry, parentVersion, graphVersion);
1247 Preconditions.checkState(parentVersion.atMost(graphVersion)
1248 && !parentVersion.equals(graphVersion),
1249 "parent entry is not older than the current graph version. "
1250 + "child: %s childEntry: %s, childVersion: %s"
1251 + "bubbleParent: %s bubbleParentEntry: %s, parentVersion: %s, graphVersion: %s",
1252 errorKey, errorEntry, childVersion,
1253 bubbleParent, bubbleParentEntry, parentVersion, graphVersion);
1254 continue;
1255 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001256 if (visitor.isInflight(bubbleParent)
1257 && bubbleParentEntry.getTemporaryDirectDeps().contains(errorKey)) {
1258 // Only bubble up to parent if it's part of this build. If this node was dirtied and
1259 // re-evaluated, but in a build without this parent, we may try to bubble up to that
1260 // parent. Don't -- it's not part of the build.
1261 // Similarly, the parent may not yet have requested this dep in its dirtiness-checking
1262 // process. Don't bubble up to it in that case either.
1263 parent = bubbleParent;
1264 parentEntry = bubbleParentEntry;
1265 break;
1266 }
1267 }
1268 if (parent == null) {
1269 Preconditions.checkState(
1270 rootValues.contains(errorKey),
1271 "Current key %s has to be a top-level key: %s, %s",
1272 errorKey,
1273 rootValues,
1274 errorEntry);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001275 break;
1276 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001277 errorKey = parent;
1278 SkyFunction factory = skyFunctions.get(parent.functionName());
1279 if (parentEntry.isDirty()) {
1280 switch (parentEntry.getDirtyState()) {
1281 case CHECK_DEPENDENCIES:
1282 // If this value's child was bubbled up to, it did not signal this value, and so we must
1283 // manually make it ready to build.
1284 parentEntry.signalDep();
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001285 // Fall through to NEEDS_REBUILDING, since state is now NEEDS_REBUILDING.
1286 case NEEDS_REBUILDING:
1287 maybeMarkRebuildingAndRemoveRemainingDirtyDirectDeps(parent, parentEntry);
1288 // Fall through to REBUILDING.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001289 case REBUILDING:
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001290 break;
1291 default:
1292 throw new AssertionError(parent + " not in valid dirty state: " + parentEntry);
1293 }
1294 }
1295 SkyFunctionEnvironment env =
Janak Ramakrishnan663f7c42015-08-31 22:19:07 +00001296 new SkyFunctionEnvironment(parent, ImmutableSet.<SkyKey>of(), bubbleErrorInfo, visitor);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001297 externalInterrupt = externalInterrupt || Thread.currentThread().isInterrupted();
1298 try {
1299 // This build is only to check if the parent node can give us a better error. We don't
1300 // care about a return value.
1301 factory.compute(parent, env);
1302 } catch (SkyFunctionException builderException) {
1303 ReifiedSkyFunctionException reifiedBuilderException =
1304 new ReifiedSkyFunctionException(builderException, parent);
1305 if (reifiedBuilderException.getRootCauseSkyKey().equals(parent)) {
Michajlo Matijkiwaa058282015-09-28 22:13:27 +00001306 error = ErrorInfo.fromException(reifiedBuilderException);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001307 bubbleErrorInfo.put(errorKey,
Michajlo Matijkiwaa058282015-09-28 22:13:27 +00001308 ValueWithMetadata.error(ErrorInfo.fromChildErrors(errorKey, ImmutableSet.of(error)),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001309 env.buildEvents(/*missingChildren=*/true)));
1310 continue;
1311 }
1312 } catch (InterruptedException interruptedException) {
1313 // Do nothing.
1314 // This throw happens if the builder requested the failed node, and then checked the
1315 // interrupted state later -- getValueOrThrow sets the interrupted bit after the failed
1316 // value is requested, to prevent the builder from doing too much work.
1317 } finally {
1318 // Clear interrupted status. We're not listening to interrupts here.
1319 Thread.interrupted();
1320 }
1321 // Builder didn't throw an exception, so just propagate this one up.
1322 bubbleErrorInfo.put(errorKey,
Michajlo Matijkiwaa058282015-09-28 22:13:27 +00001323 ValueWithMetadata.error(ErrorInfo.fromChildErrors(errorKey, ImmutableSet.of(error)),
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001324 env.buildEvents(/*missingChildren=*/true)));
1325 }
1326
1327 // Reset the interrupt bit if there was an interrupt from outside this evaluator interrupt.
1328 // Note that there are internal interrupts set in the node builder environment if an error
1329 // bubbling node calls getValueOrThrow() on a node in error.
1330 if (externalInterrupt) {
1331 Thread.currentThread().interrupt();
1332 }
1333 return bubbleErrorInfo;
1334 }
1335
1336 /**
1337 * Constructs an {@link EvaluationResult} from the {@link #graph}. Looks for cycles if there
1338 * are unfinished nodes but no error was already found through bubbling up
1339 * (as indicated by {@code bubbleErrorInfo} being null).
1340 *
1341 * <p>{@code visitor} may be null, but only in the case where all graph entries corresponding to
1342 * {@code skyKeys} are known to be in the DONE state ({@code entry.isDone()} returns true).
1343 */
1344 private <T extends SkyValue> EvaluationResult<T> constructResult(
1345 @Nullable ValueVisitor visitor, Iterable<SkyKey> skyKeys,
1346 Map<SkyKey, ValueWithMetadata> bubbleErrorInfo, boolean catastrophe) {
1347 Preconditions.checkState(!keepGoing || catastrophe || bubbleErrorInfo == null,
1348 "", skyKeys, bubbleErrorInfo);
1349 EvaluationResult.Builder<T> result = EvaluationResult.builder();
1350 List<SkyKey> cycleRoots = new ArrayList<>();
1351 boolean hasError = false;
1352 for (SkyKey skyKey : skyKeys) {
1353 ValueWithMetadata valueWithMetadata = getValueMaybeFromError(skyKey, bubbleErrorInfo);
1354 // Cycle checking: if there is a cycle, evaluation cannot progress, therefore,
1355 // the final values will not be in DONE state when the work runs out.
1356 if (valueWithMetadata == null) {
1357 // Don't look for cycles if the build failed for a known reason.
1358 if (bubbleErrorInfo == null) {
1359 cycleRoots.add(skyKey);
1360 }
1361 hasError = true;
1362 continue;
1363 }
1364 SkyValue value = valueWithMetadata.getValue();
1365 // TODO(bazel-team): Verify that message replay is fast and works in failure
1366 // modes [skyframe-core]
1367 // Note that replaying events here is only necessary on null builds, because otherwise we
1368 // would have already printed the transitive messages after building these values.
1369 replayingNestedSetEventVisitor.visit(valueWithMetadata.getTransitiveEvents());
1370 ErrorInfo errorInfo = valueWithMetadata.getErrorInfo();
1371 Preconditions.checkState(value != null || errorInfo != null, skyKey);
1372 hasError = hasError || (errorInfo != null);
1373 if (!keepGoing && errorInfo != null) {
1374 // value will be null here unless the value was already built on a prior keepGoing build.
1375 result.addError(skyKey, errorInfo);
1376 continue;
1377 }
1378 if (value == null) {
1379 // Note that we must be in the keepGoing case. Only make this value an error if it doesn't
1380 // have a value. The error shouldn't matter to the caller since the value succeeded after a
1381 // fashion.
1382 result.addError(skyKey, errorInfo);
1383 } else {
1384 result.addResult(skyKey, value);
1385 }
1386 }
1387 if (!cycleRoots.isEmpty()) {
1388 Preconditions.checkState(visitor != null, skyKeys);
1389 checkForCycles(cycleRoots, result, visitor, keepGoing);
1390 }
1391 Preconditions.checkState(bubbleErrorInfo == null || hasError,
1392 "If an error bubbled up, some top-level node must be in error", bubbleErrorInfo, skyKeys);
1393 result.setHasError(hasError);
1394 return result.build();
1395 }
1396
1397 private <T extends SkyValue> void checkForCycles(
1398 Iterable<SkyKey> badRoots, EvaluationResult.Builder<T> result, final ValueVisitor visitor,
1399 boolean keepGoing) {
1400 for (SkyKey root : badRoots) {
1401 ErrorInfo errorInfo = checkForCycles(root, visitor, keepGoing);
1402 if (errorInfo == null) {
1403 // This node just wasn't finished when evaluation aborted -- there were no cycles below it.
1404 Preconditions.checkState(!keepGoing, "", root, badRoots);
1405 continue;
1406 }
1407 Preconditions.checkState(!Iterables.isEmpty(errorInfo.getCycleInfo()),
1408 "%s was not evaluated, but was not part of a cycle", root);
1409 result.addError(root, errorInfo);
1410 if (!keepGoing) {
1411 return;
1412 }
1413 }
1414 }
1415
1416 /**
1417 * Marker value that we push onto a stack before we push a node's children on. When the marker
1418 * value is popped, we know that all the children are finished. We would use null instead, but
1419 * ArrayDeque does not permit null elements.
1420 */
1421 private static final SkyKey CHILDREN_FINISHED =
Michajlo Matijkiw02830402015-06-25 22:00:25 +00001422 new SkyKey(SkyFunctionName.create("MARKER"), "MARKER");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001423
1424 /** The max number of cycles we will report to the user for a given root, to avoid OOMing. */
1425 private static final int MAX_CYCLES = 20;
1426
1427 /**
1428 * The algorithm for this cycle detector is as follows. We visit the graph depth-first, keeping
1429 * track of the path we are currently on. We skip any DONE nodes (they are transitively
1430 * error-free). If we come to a node already on the path, we immediately construct a cycle. If
1431 * we are in the noKeepGoing case, we return ErrorInfo with that cycle to the caller. Otherwise,
1432 * we continue. Once all of a node's children are done, we construct an error value for it, based
1433 * on those children. Finally, when the original root's node is constructed, we return its
1434 * ErrorInfo.
1435 */
1436 private ErrorInfo checkForCycles(SkyKey root, ValueVisitor visitor, boolean keepGoing) {
1437 // The number of cycles found. Do not keep on searching for more cycles after this many were
1438 // found.
1439 int cyclesFound = 0;
1440 // The path through the graph currently being visited.
1441 List<SkyKey> graphPath = new ArrayList<>();
1442 // Set of nodes on the path, to avoid expensive searches through the path for cycles.
1443 Set<SkyKey> pathSet = new HashSet<>();
1444
1445 // Maintain a stack explicitly instead of recursion to avoid stack overflows
1446 // on extreme graphs (with long dependency chains).
1447 Deque<SkyKey> toVisit = new ArrayDeque<>();
1448
1449 toVisit.push(root);
1450
1451 // The procedure for this check is as follows: we visit a node, push it onto the graph stack,
1452 // push a marker value onto the toVisit stack, and then push all of its children onto the
1453 // toVisit stack. Thus, when the marker node comes to the top of the toVisit stack, we have
1454 // visited the downward transitive closure of the value. At that point, all of its children must
1455 // be finished, and so we can build the definitive error info for the node, popping it off the
1456 // graph stack.
1457 while (!toVisit.isEmpty()) {
1458 SkyKey key = toVisit.pop();
1459 NodeEntry entry = graph.get(key);
1460
1461 if (key == CHILDREN_FINISHED) {
1462 // A marker node means we are done with all children of a node. Since all nodes have
1463 // errors, we must have found errors in the children when that happens.
1464 key = graphPath.remove(graphPath.size() - 1);
Janak Ramakrishnanffa75fe2015-05-01 03:33:58 +00001465 entry = Preconditions.checkNotNull(graph.get(key), key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001466 pathSet.remove(key);
1467 // Skip this node if it was first/last node of a cycle, and so has already been processed.
1468 if (entry.isDone()) {
1469 continue;
1470 }
1471 if (!keepGoing) {
1472 // in the --nokeep_going mode, we would have already returned if we'd found a cycle below
1473 // this node. The fact that we haven't means that there were no cycles below this node
1474 // -- it just hadn't finished evaluating. So skip it.
1475 continue;
1476 }
1477 if (cyclesFound < MAX_CYCLES) {
1478 // Value must be ready, because all of its children have finished, so we can build its
1479 // error.
1480 Preconditions.checkState(entry.isReady(), "%s not ready. ValueEntry: %s", key, entry);
1481 } else if (!entry.isReady()) {
1482 removeIncompleteChildrenForCycle(key, entry, entry.getTemporaryDirectDeps());
1483 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001484 maybeMarkRebuildingAndRemoveRemainingDirtyDirectDeps(key, entry);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001485 Set<SkyKey> directDeps = entry.getTemporaryDirectDeps();
1486 // Find out which children have errors. Similar logic to that in Evaluate#run().
1487 List<ErrorInfo> errorDeps = getChildrenErrorsForCycle(directDeps);
1488 Preconditions.checkState(!errorDeps.isEmpty(),
1489 "Value %s was not successfully evaluated, but had no child errors. ValueEntry: %s", key,
1490 entry);
1491 SkyFunctionEnvironment env = new SkyFunctionEnvironment(key, directDeps, visitor);
Michajlo Matijkiwaa058282015-09-28 22:13:27 +00001492 env.setError(ErrorInfo.fromChildErrors(key, errorDeps));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001493 env.commit(/*enqueueParents=*/false);
1494 }
1495
Janak Ramakrishnanffa75fe2015-05-01 03:33:58 +00001496 Preconditions.checkNotNull(entry, key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001497 // Nothing to be done for this node if it already has an entry.
1498 if (entry.isDone()) {
1499 continue;
1500 }
1501 if (cyclesFound == MAX_CYCLES) {
1502 // Do not keep on searching for cycles indefinitely, to avoid excessive runtime/OOMs.
1503 continue;
1504 }
1505
1506 if (pathSet.contains(key)) {
1507 int cycleStart = graphPath.indexOf(key);
1508 // Found a cycle!
1509 cyclesFound++;
1510 Iterable<SkyKey> cycle = graphPath.subList(cycleStart, graphPath.size());
1511 // Put this node into a consistent state for building if it is dirty.
Nathan Harmatab408f9e2015-02-10 02:13:05 +00001512 if (entry.isDirty() && entry.getDirtyState() == NodeEntry.DirtyState.CHECK_DEPENDENCIES) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001513 // In the check deps state, entry has exactly one child not done yet. Note that this node
1514 // must be part of the path to the cycle we have found (since done nodes cannot be in
1515 // cycles, and this is the only missing one). Thus, it will not be removed below in
1516 // removeDescendantsOfCycleValue, so it is safe here to signal that it is done.
1517 entry.signalDep();
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001518 maybeMarkRebuildingAndRemoveRemainingDirtyDirectDeps(key, entry);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001519 }
1520 if (keepGoing) {
1521 // Any children of this node that we haven't already visited are not worth visiting,
1522 // since this node is about to be done. Thus, the only child worth visiting is the one in
1523 // this cycle, the cycleChild (which may == key if this cycle is a self-edge).
1524 SkyKey cycleChild = selectCycleChild(key, graphPath, cycleStart);
1525 removeDescendantsOfCycleValue(key, entry, cycleChild, toVisit,
1526 graphPath.size() - cycleStart);
1527 ValueWithMetadata dummyValue = ValueWithMetadata.wrapWithMetadata(new SkyValue() {});
1528
1529
1530 SkyFunctionEnvironment env =
1531 new SkyFunctionEnvironment(key, entry.getTemporaryDirectDeps(),
1532 ImmutableMap.of(cycleChild, dummyValue), visitor);
1533
1534 // Construct error info for this node. Get errors from children, which are all done
1535 // except possibly for the cycleChild.
1536 List<ErrorInfo> allErrors =
1537 getChildrenErrors(entry.getTemporaryDirectDeps(), /*unfinishedChild=*/cycleChild);
1538 CycleInfo cycleInfo = new CycleInfo(cycle);
1539 // Add in this cycle.
Michajlo Matijkiwaa058282015-09-28 22:13:27 +00001540 allErrors.add(ErrorInfo.fromCycle(cycleInfo));
1541 env.setError(ErrorInfo.fromChildErrors(key, allErrors));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001542 env.commit(/*enqueueParents=*/false);
1543 continue;
1544 } else {
1545 // We need to return right away in the noKeepGoing case, so construct the cycle (with the
1546 // path) and return.
1547 Preconditions.checkState(graphPath.get(0).equals(root),
1548 "%s not reached from %s. ValueEntry: %s", key, root, entry);
Michajlo Matijkiwaa058282015-09-28 22:13:27 +00001549 return ErrorInfo.fromCycle(new CycleInfo(graphPath.subList(0, cycleStart), cycle));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001550 }
1551 }
1552
1553 // This node is not yet known to be in a cycle. So process its children.
Janak Ramakrishnan6d9ca5b2015-08-31 19:22:06 +00001554 Iterable<SkyKey> children = entry.getTemporaryDirectDeps();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001555 if (Iterables.isEmpty(children)) {
1556 continue;
1557 }
Janak Ramakrishnan6d9ca5b2015-08-31 19:22:06 +00001558 // Prefetch all children, in case our graph performs better with a primed cache.
1559 graph.getBatch(children);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001560
1561 // This marker flag will tell us when all this node's children have been processed.
1562 toVisit.push(CHILDREN_FINISHED);
1563 // This node is now part of the path through the graph.
1564 graphPath.add(key);
1565 pathSet.add(key);
1566 for (SkyKey nextValue : children) {
1567 toVisit.push(nextValue);
1568 }
1569 }
1570 return keepGoing ? getAndCheckDone(root).getErrorInfo() : null;
1571 }
1572
1573 /**
1574 * Returns the child of this node that is in the cycle that was just found. If the cycle is a
1575 * self-edge, returns the node itself.
1576 */
1577 private static SkyKey selectCycleChild(SkyKey key, List<SkyKey> graphPath, int cycleStart) {
1578 return cycleStart + 1 == graphPath.size() ? key : graphPath.get(cycleStart + 1);
1579 }
1580
1581 /**
1582 * Get all the errors of child nodes. There must be at least one cycle amongst them.
1583 *
1584 * @param children child nodes to query for errors.
1585 * @return List of ErrorInfos from all children that had errors.
1586 */
1587 private List<ErrorInfo> getChildrenErrorsForCycle(Iterable<SkyKey> children) {
1588 List<ErrorInfo> allErrors = new ArrayList<>();
1589 boolean foundCycle = false;
1590 for (SkyKey child : children) {
1591 ErrorInfo errorInfo = getAndCheckDone(child).getErrorInfo();
1592 if (errorInfo != null) {
1593 foundCycle |= !Iterables.isEmpty(errorInfo.getCycleInfo());
1594 allErrors.add(errorInfo);
1595 }
1596 }
1597 Preconditions.checkState(foundCycle, "", children, allErrors);
1598 return allErrors;
1599 }
1600
1601 /**
1602 * Get all the errors of child nodes.
1603 *
1604 * @param children child nodes to query for errors.
1605 * @param unfinishedChild child which is allowed to not be done.
1606 * @return List of ErrorInfos from all children that had errors.
1607 */
1608 private List<ErrorInfo> getChildrenErrors(Iterable<SkyKey> children, SkyKey unfinishedChild) {
1609 List<ErrorInfo> allErrors = new ArrayList<>();
1610 for (SkyKey child : children) {
1611 ErrorInfo errorInfo = getErrorMaybe(child, /*allowUnfinished=*/child.equals(unfinishedChild));
1612 if (errorInfo != null) {
1613 allErrors.add(errorInfo);
1614 }
1615 }
1616 return allErrors;
1617 }
1618
1619 @Nullable
1620 private ErrorInfo getErrorMaybe(SkyKey key, boolean allowUnfinished) {
1621 if (!allowUnfinished) {
1622 return getAndCheckDone(key).getErrorInfo();
1623 }
1624 NodeEntry entry = Preconditions.checkNotNull(graph.get(key), key);
1625 return entry.isDone() ? entry.getErrorInfo() : null;
1626 }
1627
1628 /**
1629 * Removes direct children of key from toVisit and from the entry itself, and makes the entry
1630 * ready if necessary. We must do this because it would not make sense to try to build the
1631 * children after building the entry. It would violate the invariant that a parent can only be
1632 * built after its children are built; See bug "Precondition error while evaluating a Skyframe
1633 * graph with a cycle".
1634 *
1635 * @param key SkyKey of node in a cycle.
1636 * @param entry NodeEntry of node in a cycle.
1637 * @param cycleChild direct child of key in the cycle, or key itself if the cycle is a self-edge.
1638 * @param toVisit list of remaining nodes to visit by the cycle-checker.
1639 * @param cycleLength the length of the cycle found.
1640 */
1641 private void removeDescendantsOfCycleValue(SkyKey key, NodeEntry entry,
1642 @Nullable SkyKey cycleChild, Iterable<SkyKey> toVisit, int cycleLength) {
1643 Set<SkyKey> unvisitedDeps = new HashSet<>(entry.getTemporaryDirectDeps());
1644 unvisitedDeps.remove(cycleChild);
1645 // Remove any children from this node that are not part of the cycle we just found. They are
1646 // irrelevant to the node as it stands, and if they are deleted from the graph because they are
1647 // not built by the end of cycle-checking, we would have dangling references.
1648 removeIncompleteChildrenForCycle(key, entry, unvisitedDeps);
1649 if (!entry.isReady()) {
1650 // The entry has at most one undone dep now, its cycleChild. Signal to make entry ready. Note
1651 // that the entry can conceivably be ready if its cycleChild already found a different cycle
1652 // and was built.
1653 entry.signalDep();
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001654 maybeMarkRebuildingAndRemoveRemainingDirtyDirectDeps(key, entry);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001655 }
1656 Preconditions.checkState(entry.isReady(), "%s %s %s", key, cycleChild, entry);
1657 Iterator<SkyKey> it = toVisit.iterator();
1658 while (it.hasNext()) {
1659 SkyKey descendant = it.next();
1660 if (descendant == CHILDREN_FINISHED) {
1661 // Marker value, delineating the end of a group of children that were enqueued.
1662 cycleLength--;
1663 if (cycleLength == 0) {
1664 // We have seen #cycleLength-1 marker values, and have arrived at the one for this value,
1665 // so we are done.
1666 return;
1667 }
1668 continue; // Don't remove marker values.
1669 }
1670 if (cycleLength == 1) {
1671 // Remove the direct children remaining to visit of the cycle node.
1672 Preconditions.checkState(unvisitedDeps.contains(descendant),
1673 "%s %s %s %s %s", key, descendant, cycleChild, unvisitedDeps, entry);
1674 it.remove();
1675 }
1676 }
1677 throw new IllegalStateException("There were not " + cycleLength + " groups of children in "
1678 + toVisit + " when trying to remove children of " + key + " other than " + cycleChild);
1679 }
1680
1681 private void removeIncompleteChildrenForCycle(SkyKey key, NodeEntry entry,
1682 Iterable<SkyKey> children) {
1683 Set<SkyKey> unfinishedDeps = new HashSet<>();
1684 for (SkyKey child : children) {
1685 if (removeIncompleteChild(key, child)) {
1686 unfinishedDeps.add(child);
1687 }
1688 }
1689 entry.removeUnfinishedDeps(unfinishedDeps);
1690 }
1691
1692 private NodeEntry getAndCheckDone(SkyKey key) {
1693 NodeEntry entry = graph.get(key);
1694 Preconditions.checkNotNull(entry, key);
1695 Preconditions.checkState(entry.isDone(), "%s %s", key, entry);
1696 return entry;
1697 }
1698
Nathan Harmata6fc9fa02015-03-27 17:48:57 +00001699 private ValueWithMetadata maybeWrapValueFromError(SkyKey key, @Nullable NodeEntry entry,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001700 @Nullable Map<SkyKey, ValueWithMetadata> bubbleErrorInfo) {
1701 SkyValue value = bubbleErrorInfo == null ? null : bubbleErrorInfo.get(key);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001702 if (value != null) {
1703 Preconditions.checkNotNull(entry,
1704 "Value cannot have error before evaluation started", key, value);
1705 return ValueWithMetadata.wrapWithMetadata(value);
1706 }
Janak Ramakrishnan0afd4532015-08-24 17:27:48 +00001707 return isDoneForBuild(entry)
1708 ? ValueWithMetadata.wrapWithMetadata(entry.getValueMaybeWithMetadata())
1709 : null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001710 }
1711
Nathan Harmata6fc9fa02015-03-27 17:48:57 +00001712 @Nullable
1713 private ValueWithMetadata getValueMaybeFromError(SkyKey key,
1714 @Nullable Map<SkyKey, ValueWithMetadata> bubbleErrorInfo) {
1715 return maybeWrapValueFromError(key, graph.get(key), bubbleErrorInfo);
1716 }
1717
1718 private Map<SkyKey, ValueWithMetadata> getValuesMaybeFromError(Iterable<SkyKey> keys,
1719 @Nullable Map<SkyKey, ValueWithMetadata> bubbleErrorInfo) {
1720 Map<SkyKey, NodeEntry> entries = graph.getBatch(ImmutableSet.copyOf(keys));
1721 ImmutableMap.Builder<SkyKey, ValueWithMetadata> builder = ImmutableMap.builder();
1722 for (SkyKey key : keys) {
1723 ValueWithMetadata valueWithMetadata = maybeWrapValueFromError(key, entries.get(key),
1724 bubbleErrorInfo);
1725 if (valueWithMetadata != null) {
1726 builder.put(key, valueWithMetadata);
1727 }
1728 }
1729 return builder.build();
1730 }
1731
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001732 /**
1733 * Return true if the entry does not need to be re-evaluated this build. The entry will need to
1734 * be re-evaluated if it is not done, but also if it was not completely evaluated last build and
1735 * this build is keepGoing.
1736 */
1737 private boolean isDoneForBuild(@Nullable NodeEntry entry) {
1738 return entry != null && entry.isDone();
1739 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +00001740
1741 static void injectValue(
1742 SkyKey key,
1743 SkyValue value,
1744 Version version,
1745 EvaluableGraph graph,
1746 DirtyKeyTracker dirtyKeyTracker) {
1747 Preconditions.checkNotNull(value, key);
1748 NodeEntry prevEntry = graph.createIfAbsent(key);
1749 DependencyState newState = prevEntry.addReverseDepAndCheckIfDone(null);
1750 Preconditions.checkState(
1751 newState != DependencyState.ALREADY_EVALUATING, "%s %s", key, prevEntry);
1752 if (prevEntry.isDirty()) {
1753 Preconditions.checkState(
1754 newState == DependencyState.NEEDS_SCHEDULING, "%s %s", key, prevEntry);
1755 // There was an existing entry for this key in the graph.
1756 // Get the node in the state where it is able to accept a value.
1757
1758 // Check that the previous node has no dependencies. Overwriting a value with deps with an
1759 // injected value (which is by definition deps-free) needs a little additional bookkeeping
1760 // (removing reverse deps from the dependencies), but more importantly it's something that
1761 // we want to avoid, because it indicates confusion of input values and derived values.
1762 Preconditions.checkState(
1763 prevEntry.noDepsLastBuild(), "existing entry for %s has deps: %s", key, prevEntry);
1764 // Put the node into a "rebuilding" state and verify that there were no dirty deps remaining.
1765 Preconditions.checkState(
1766 prevEntry.markRebuildingAndGetAllRemainingDirtyDirectDeps().isEmpty(),
1767 "%s %s",
1768 key,
1769 prevEntry);
1770 }
1771 prevEntry.setValue(value, version);
1772 // Now that this key's injected value is set, it is no longer dirty.
1773 dirtyKeyTracker.notDirty(key);
1774 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001775}