// Copyright 2014 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.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.events.ExtendedEventHandler; import com.google.devtools.build.lib.util.GroupedList; import java.util.Map; import javax.annotation.Nullable; /** * Machinery to evaluate a single value. * *

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. * *

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. * *

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. * *

This method may return {@link Restart} in rare circumstances. See its docs. Do not return * values of this type unless you know exactly what you are doing. * * @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}. * *

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); /** * Sentinel {@link SkyValue} type for {@link #compute} to return, indicating that something went * wrong, and that the evaluation returning this value must be restarted, and the nodes associated * with the specified keys (which should be direct or transitive dependencies of the failed * evaluation) must also be restarted. * *

An intended cause for returning this is external data loss; e.g., if a dependency's * "done-ness" is intended to mean that certain data is available in an external system, but * during evaluation of a node that depends on that external data, that data has gone missing, and * reevaluation of the dependency is expected to repair the discrepancy. * *

Values of this type will never be returned by {@link Environment}'s getValue * methods or from {@link NodeEntry#getValue()}. * *

TODO(mschaller): the ability to specify arbitrary additional keys to restart is error-prone. * It would be safer to require nodes requesting restarts to provide dependency paths, which the * framework could efficiently verify before restarting. */ interface Restart extends SkyValue { Restart SELF = ImmutableList::of; static Restart selfAnd(SkyKey... additionalKeysToRestart) { return selfAnd(ImmutableList.copyOf(additionalKeysToRestart)); } static Restart selfAnd(ImmutableList additionalKeysToRestart) { return () -> additionalKeysToRestart; } ImmutableList getAdditionalKeysToRestart(); } /** * The services provided to the {@link SkyFunction#compute} implementation by the Skyframe * evaluation framework. */ 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. * *

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). * *

This method and the ones below may throw {@link InterruptedException}. Such exceptions * must not be caught by the {@link SkyFunction#compute} implementation. Instead, they should be * propagated up to the caller of {@link SkyFunction#compute}. */ @Nullable SkyValue getValue(SkyKey valueName) throws InterruptedException; /** * 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. * *

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 SkyValue getValueOrThrow(SkyKey depKey, Class exceptionClass) throws E, InterruptedException; @Nullable SkyValue getValueOrThrow( SkyKey depKey, Class exceptionClass1, Class exceptionClass2) throws E1, E2, InterruptedException; @Nullable SkyValue getValueOrThrow( SkyKey depKey, Class exceptionClass1, Class exceptionClass2, Class exceptionClass3) throws E1, E2, E3, InterruptedException; @Nullable SkyValue getValueOrThrow( SkyKey depKey, Class exceptionClass1, Class exceptionClass2, Class exceptionClass3, Class exceptionClass4) throws E1, E2, E3, E4, InterruptedException; @Nullable < E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception, E5 extends Exception> SkyValue getValueOrThrow( SkyKey depKey, Class exceptionClass1, Class exceptionClass2, Class exceptionClass3, Class exceptionClass4, Class exceptionClass5) throws E1, E2, E3, E4, E5, InterruptedException; /** * 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. * *

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. * *

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. * *

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. * *

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}. * *

Example of when not to use getValues: A BestMatch value is built with key * <potentialMatchesRef, matchCriterion>. 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 incorrect 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. * *

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 getValues(Iterable depKeys) throws InterruptedException; /** * 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. * *

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. * *

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. */ Map> getValuesOrThrow( Iterable depKeys, Class exceptionClass) throws InterruptedException; Map> getValuesOrThrow( Iterable depKeys, Class exceptionClass1, Class exceptionClass2) throws InterruptedException; Map> getValuesOrThrow( Iterable depKeys, Class exceptionClass1, Class exceptionClass2, Class exceptionClass3) throws InterruptedException; Map> getValuesOrThrow( Iterable depKeys, Class exceptionClass1, Class exceptionClass2, Class exceptionClass3, Class exceptionClass4) throws InterruptedException; < E1 extends Exception, E2 extends Exception, E3 extends Exception, E4 extends Exception, E5 extends Exception> Map> getValuesOrThrow( Iterable depKeys, Class exceptionClass1, Class exceptionClass2, Class exceptionClass3, Class exceptionClass4, Class exceptionClass5) throws InterruptedException; /** * 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: * *

* *

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}. */ ExtendedEventHandler getListener(); /** * A live view of deps known to have already been requested either through an earlier call to * {@link SkyFunction#compute} or inferred during change pruning. Should return {@code null} if * unknown. */ @Nullable default GroupedList getTemporaryDirectDeps() { return null; } /** * Register dependencies on keys without necessarily requiring their values. * *

WARNING: Dependencies here MUST be done! Only use this function if you know what you're * doing. */ default void registerDependencies(Iterable keys) throws InterruptedException { getValues(keys); } /** Returns whether we are currently in error bubbling. */ @VisibleForTesting boolean inErrorBubblingForTesting(); } }