aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/skyframe/DirtyTrackingProgressReceiver.java
blob: a8ad00cecd0015bac641ad9c1b65cad30b2e1f06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// 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.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.function.Supplier;
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 stateStarting(SkyKey skyKey, NodeState nodeState) {
    if (progressReceiver != null) {
      progressReceiver.stateStarting(skyKey, nodeState);
    }
  }

  @Override
  public void stateEnding(SkyKey skyKey, NodeState nodeState, long elapsedTimeNanos) {
    if (progressReceiver != null) {
      progressReceiver.stateEnding(skyKey, nodeState, elapsedTimeNanos);
    }
  }

  @Override
  public void evaluated(
      SkyKey skyKey,
      @Nullable SkyValue value,
      Supplier<EvaluationSuccessState> evaluationSuccessState,
      EvaluationState state) {
    if (progressReceiver != null) {
      progressReceiver.evaluated(skyKey, value, evaluationSuccessState, 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);
  }
}