aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java
blob: 58bb846487968c12456b02eb081733742dde3553 (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Copyright 2014 Google Inc. 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.annotations.VisibleForTesting;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.events.EventHandler;

import java.util.Map;

import javax.annotation.Nullable;

/**
 * Machinery to evaluate a single value.
 *
 * <p>The SkyFunction {@link #compute} implementation is supposed to access only direct
 * dependencies of the value. However, the direct dependencies need not be known in advance. The
 * implementation can request arbitrary values using {@link Environment#getValue}. If the values
 * are not ready, the call will return {@code null}; in that case the implementation should just
 * return {@code null}, in which case the missing dependencies will be computed and the {@link
 * #compute} method will be started again.
 * */
public interface SkyFunction {

  /**
   * When a value is requested, this method is called with the name of the value and a
   * dependency-tracking environment.
   *
   * <p>This method should return a non-{@code null} value, or {@code null} if any dependencies
   * were missing ({@link Environment#valuesMissing} was true before returning). In that case the
   * missing dependencies will be computed and the {@code compute} method called again.
   *
   * <p>This method should throw if it fails, or if one of its dependencies fails with an
   * exception and this method cannot recover. If one of its dependencies fails and this method can
   * enrich the exception with additional context, then this method should catch that exception and
   * throw another containing that additional context. If it has no such additional context, then
   * it should allow its dependency's exception to be thrown through it.
   *
   * @throws SkyFunctionException on failure
   * @throws InterruptedException if interrupted
   */
  @ThreadSafe
  @Nullable
  SkyValue compute(SkyKey skyKey, Environment env)
      throws SkyFunctionException, InterruptedException;

  /**
   * Extracts a tag (target label) from a SkyKey if it has one. Otherwise return {@code null}.
   *
   * <p>The tag is used for filtering out non-error event messages that do not match
   * --output_filter flag. If a SkyFunction returns {@code null} in this method it means that all
   * the info/warning messages associated with this value will be shown, no matter what
   * --output_filter says.
   */
  @Nullable
  String extractTag(SkyKey skyKey);

  /**
   * The services provided to the {@link SkyFunction} implementation by the graph implementation.
   */
  interface Environment {
    /**
     * Returns a direct dependency. If the specified value is not in the set of already evaluated
     * direct dependencies, returns {@code null}. Also returns {@code null} if the specified
     * value has already been evaluated and found to be in error.
     *
     * <p>On a subsequent evaluation, if any of this value's dependencies have changed they will be
     * re-evaluated in the same order as originally requested by the {@code SkyFunction} using
     * this {@code getValue} call (see {@link #getValues} for when preserving the order is not
     * important).
     */
    @Nullable
    SkyValue getValue(SkyKey valueName);

    /**
     * Returns a direct dependency. If the specified value is not in the set of already evaluated
     * direct dependencies, returns {@code null}. If the specified value has already been
     * evaluated and found to be in error, throws the exception coming from the error, so long as
     * the exception is of one of the specified types. SkyFunction implementations may use this
     * method to continue evaluation even if one of their dependencies is in error by catching
     * the thrown exception and proceeding. The caller must specify the exception type(s) that
     * might be thrown using the {@code exceptionClass} argument(s). If the dependency's
     * exception is not an instance of {@code exceptionClass}, {@code null} is returned.
     *
     * <p>The exception class given cannot be a supertype or a subtype of {@link RuntimeException},
     * or a subtype of {@link InterruptedException}. See
     * {@link SkyFunctionException#validateExceptionType} for details.
     */
    @Nullable
    <E extends Exception> SkyValue getValueOrThrow(SkyKey depKey, Class<E> exceptionClass) throws E;
    @Nullable
    <E1 extends Exception, E2 extends Exception> SkyValue getValueOrThrow(SkyKey depKey,
        Class<E1> exceptionClass1, Class<E2> exceptionClass2) throws E1, E2;
    @Nullable
    <E1 extends Exception, E2 extends Exception, E3 extends Exception> SkyValue getValueOrThrow(
        SkyKey depKey, Class<E1> exceptionClass1, Class<E2> exceptionClass2,
        Class<E3> exceptionClass3) throws E1, E2, E3;
    @Nullable
    <E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
        SkyValue getValueOrThrow(SkyKey depKey, Class<E1> exceptionClass1,
        Class<E2> exceptionClass2, Class<E3> exceptionClass3, Class<E4> exceptionClass4)
            throws E1, E2, E3, E4;

    /**
     * Requests {@code depKeys} "in parallel", independent of each others' values. These keys may be
     * thought of as a "dependency group" -- they are requested together by this value.
     *
     * <p>In general, if the result of one getValue call can affect the argument of a later getValue
     * call, the two calls cannot be merged into a single getValues call, since the result of the
     * first call might change on a later evaluation. Inversely, if the result of one getValue call
     * cannot affect the parameters of the next getValue call, the two keys can form a dependency
     * group and the two getValue calls merged into one getValues call.
     *
     * <p>This means that on subsequent evaluations, when checking to see if dependencies require
     * re-evaluation, all the values in this group may be simultaneously checked. A SkyFunction
     * should request a dependency group if checking the deps serially on a subsequent evaluation
     * would take too long, and if the {@link #compute} method would request all deps anyway as
     * long as no earlier deps had changed. SkyFunction.Environment implementations may also
     * choose to request these deps in parallel on the first evaluation, potentially speeding it up.
     *
     * <p>While re-evaluating every value in the group may take longer than re-evaluating just
     * the first one and finding that it has changed, no extra work is done: the contract of the
     * dependency group means that the {@link #compute} method, when called to re-evaluate this
     * value, will request all values in the group again anyway, so they would have to have been
     * built in any case.
     *
     * <p>Example of when to use getValues: A ListProcessor value is built with key inputListRef.
     * The {@link #compute} method first calls getValue(InputList.key(inputListRef)), and
     * retrieves inputList. It then iterates through inputList, calling getValue on each input.
     * Finally, it processes the whole list and returns. Say inputList is (a, b, c). Since the
     * {@link #compute} method will unconditionally call getValue(a), getValue(b), and getValue
     * (c), the {@link #compute} method can instead just call getValues({a, b, c}). If the value
     * is later dirtied the evaluator will evaluate a, b, and c in parallel (assuming the inputList
     * value was unchanged), and re-evaluate the ListProcessor value only if at least one of them
     * was changed. On the other hand, if the InputList changes to be (a, b, d), then the
     * evaluator will see that the first dep has changed, and call the {@link #compute} method to
     * re-evaluate from scratch, without considering the dep group of {a, b, c}.
     *
     * <p>Example of when not to use getValues: A BestMatch value is built with key
     * &lt;potentialMatchesRef, matchCriterion&gt;. The {@link #compute} method first calls
     * getValue(PotentialMatches.key(potentialMatchesRef) and retrieves potentialMatches. It then
     * iterates through potentialMatches, calling getValue on each potential match until it finds
     * one that satisfies matchCriterion. In this case, if potentialMatches is (a, b, c), it would
     * be <i>incorrect</i> to call getValues({a, b, c}), because it is not known yet whether
     * requesting b or c will be necessary -- if a matches, then we will never call b or c.
     *
     * <p>Returns a map, {@code m}. For all {@code k} in {@code depKeys}, {@code m.containsKey(k)}
     * is {@code true}, and, {@code m.get(k) != null} iff the dependency was already evaluated and
     * was not in error.
     */
    Map<SkyKey, SkyValue> getValues(Iterable<SkyKey> depKeys);

    /**
     * Similar to {@link #getValues} but allows the caller to specify a set of types that are
     * proper subtypes of Exception (see {@link SkyFunctionException} for more details) to find
     * out whether any of the dependencies' evaluations resulted in exceptions of those types.
     * The returned objects may throw when attempting to retrieve their value.
     *
     * <p>Callers should prioritize their responsibility to detect and handle errors in the
     * returned map over their responsibility to return {@code null} if values are missing. This
     * is because in nokeep_going evaluations, an error from a low level dependency is given a
     * chance to be enriched by its reverse-dependencies, if possible.
     *
     * <p>Returns a map, {@code m}. For all {@code k} in {@code depKeys}, {@code m.get(k) !=
     * null}. For all {@code v} such that there is some {@code k} such that {@code m.get(k) ==
     * v}, the following is true: {@code v.get() != null} iff the dependency {@code k} was
     * already evaluated and was not in error. {@code v.get()} throws {@code E} iff the
     * dependency {@code k} was already evaluated with an error in the specified set of {@link
     * Exception} types.
     */
    <E extends Exception> Map<SkyKey, ValueOrException<E>> getValuesOrThrow(
        Iterable<SkyKey> depKeys, Class<E> exceptionClass);
    <E1 extends Exception, E2 extends Exception> Map<SkyKey, ValueOrException2<E1, E2>>
    getValuesOrThrow(Iterable<SkyKey> depKeys, Class<E1> exceptionClass1,
        Class<E2> exceptionClass2);
    <E1 extends Exception, E2 extends Exception, E3 extends Exception>
    Map<SkyKey, ValueOrException3<E1, E2, E3>> getValuesOrThrow(Iterable<SkyKey> depKeys,
        Class<E1> exceptionClass1, Class<E2> exceptionClass2, Class<E3> exceptionClass3);
    <E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception>
    Map<SkyKey, ValueOrException4<E1, E2, E3, E4>> getValuesOrThrow(Iterable<SkyKey> depKeys,
        Class<E1> exceptionClass1, Class<E2> exceptionClass2, Class<E3> exceptionClass3,
        Class<E4> exceptionClass4);

    /**
     * Returns whether there was a previous getValue[s][OrThrow] that indicated a missing
     * dependency. Formally, returns true iff at least one of the following occurred:
     *
     * <ul>
     *   <li>getValue[OrThrow](k[, c]) returned {@code null} for some k</li>
     *   <li>getValues(ks).get(k) == {@code null} for some ks and k such that ks.contains(k)</li>
     *   <li>
     *     getValuesOrThrow(ks, c).get(k).get() == {@code null} for some ks and k such that
     *     ks.contains(k)
     *   </li>
     * </ul>
     *
     * <p>If this returns true, the {@link SkyFunction} must return {@code null}.
     */
    boolean valuesMissing();

    /**
     * Returns the {@link EventHandler} that a SkyFunction should use to print any errors,
     * warnings, or progress messages during execution of {@link SkyFunction#compute}.
     */
    EventHandler getListener();

    /** Returns whether we are currently in error bubbling. */
    @VisibleForTesting
    boolean inErrorBubblingForTesting();
  }
}