// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.skyframe; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.util.Set; import javax.annotation.Nullable; /** * A delegating {@link EvaluationProgressReceiver} that tracks inflight nodes, nodes which * are being evaluated or scheduled for evaluation, and dirty nodes. */ public class DirtyTrackingProgressReceiver implements EvaluationProgressReceiver { @Nullable private final EvaluationProgressReceiver progressReceiver; private final Set<SkyKey> dirtyKeys = Sets.newConcurrentHashSet(); private Set<SkyKey> inflightKeys = Sets.newConcurrentHashSet(); public DirtyTrackingProgressReceiver(@Nullable EvaluationProgressReceiver progressReceiver) { this.progressReceiver = progressReceiver; } /** Called when a node is injected into the graph, and not evaluated. */ protected void injected(SkyKey skyKey) { // This node was never evaluated, but is now clean and need not be re-evaluated inflightKeys.remove(skyKey); removeFromDirtySet(skyKey); } @Override public void invalidated(SkyKey skyKey, InvalidationState state) { if (progressReceiver != null) { progressReceiver.invalidated(skyKey, state); } switch (state) { case DELETED: // This key was removed from the graph, so no longer needs to be marked as dirty. removeFromDirtySet(skyKey); break; case DIRTY: addToDirtySet(skyKey); break; default: throw new IllegalStateException(state.toString()); } } @Override public void enqueueing(SkyKey skyKey) { enqueueing(skyKey, false); } /** * Called when a node was requested to be enqueued but wasn't because either an interrupt or * an error (in nokeep_going mode) had occurred. */ protected void enqueueAfterError(SkyKey skyKey) { enqueueing(skyKey, true); } private void enqueueing(SkyKey skyKey, boolean afterError) { // We unconditionally add the key to the set of in-flight nodes even if evaluation is never // scheduled, because we still want to remove the previously created NodeEntry from the graph. // Otherwise we would leave the graph in a weird state (wasteful garbage in the best case and // inconsistent in the worst case). boolean newlyEnqueued = inflightKeys.add(skyKey); if (newlyEnqueued) { // All nodes enqueued for evaluation will be either verified clean, re-evaluated, or cleaned // up after being in-flight when an error happens in nokeep_going mode or in the event of an // interrupt. In any of these cases, they won't be dirty anymore. removeFromDirtySet(skyKey); if (progressReceiver != null && !afterError) { // Only tell the external listener the node was enqueued if no there was neither an error // or interrupt. progressReceiver.enqueueing(skyKey); } } } @Override public void computing(SkyKey skyKey) { if (progressReceiver != null) { progressReceiver.computing(skyKey); } } @Override public void computed(SkyKey skyKey, long elapsedTimeNanos) { if (progressReceiver != null) { progressReceiver.computed(skyKey, elapsedTimeNanos); } } @Override public void evaluated(SkyKey skyKey, Supplier<SkyValue> valueSupplier, EvaluationState state) { if (progressReceiver != null) { progressReceiver.evaluated(skyKey, valueSupplier, state); } // This key was either built or marked clean, so we can remove it from both the dirty and // inflight nodes. inflightKeys.remove(skyKey); removeFromDirtySet(skyKey); } /** Returns if the key is enqueued for evaluation. */ protected boolean isInflight(SkyKey skyKey) { return inflightKeys.contains(skyKey); } /** Returns the set of all keys that are enqueued for evaluation, and resets the set to empty. */ protected Set<SkyKey> getAndClearInflightKeys() { Set<SkyKey> keys = inflightKeys; inflightKeys = Sets.newConcurrentHashSet(); return keys; } /** * Returns the set of all dirty keys that have not been enqueued. * This is useful for garbage collection, where we would not want to remove dirty nodes that are * needed for evaluation (in the downward transitive closure of the set of the evaluation's * top level nodes). */ protected Set<SkyKey> getUnenqueuedDirtyKeys(){ return ImmutableSet.copyOf(dirtyKeys); } protected void addToDirtySet(SkyKey skyKey) { dirtyKeys.add(skyKey); } protected void removeFromDirtySet(SkyKey skyKey) { dirtyKeys.remove(skyKey); } }