| // Copyright 2022 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.state; | |
| import com.google.devtools.build.skyframe.SkyFunction; | |
| import com.google.devtools.build.skyframe.SkyFunction.Environment.SkyKeyComputeState; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import java.util.function.Consumer; | |
| import javax.annotation.Nullable; | |
| /** | |
| * A simple state machine with structured concurrency. | |
| * | |
| * <p>This is used to implement {@link SkyFunction}s with logical concurrency within {@link | |
| * SkyKeyComputeState}. All execution is singly-threaded. It can be used in places where further | |
| * stateful decomposition of a computation is desirable but more {@code Skyframe} entries would | |
| * create too much overhead. However, the key motivation is to facilitate logical concurrency. | |
| * | |
| * <p>For example, consider a {@link SkyFunction} that processes dependencies. Each dependency | |
| * requires a sequence of processing steps, some of which have {@code Skyframe} lookups. Let {@code | |
| * A = <A1, A2, A3>} and {@code B = <B1, B2, B3>} be sequences of dependent {@code SkyKey}s. {@code | |
| * A3} depends on {@code A2} depends on {@code A1} and similarly for {@code B3, B2, B1}. The | |
| * processing of {@code A} and {@code B} are independent and therefore logically concurrent. | |
| * | |
| * <p>One conventional approach is to implement the {@code SkyFunction} to make groups like {@code | |
| * (A1,B1), (A2,B2), (A3,B3)}. Grouping is more efficient than one-at-a-time lookups because in | |
| * builds where lookups can be performed remotely, such implementations can parallelize over groups | |
| * but process single queries sequentially to avoid wasted speculative work. | |
| * | |
| * <p>However, this manual batching may creates false dependencies that lead to unnecessary latency. | |
| * In the example, {@code A2} can't be evaluated until {@code B1} is available. If both {@code B1} | |
| * and {@code A2} are slow, the critical path is unnecessarily lengthened by forcing {@code A2} to | |
| * happen after {@code B1} instead of allowing {@code A2} to proceed once {@code A1} is available. | |
| * From the perspective of restarts, if both {@code B1} and {@code A2} require restarts, evaluation | |
| * requires 2 restarts. However, by running concurrently, the restart of {@code B1} and {@code A2} | |
| * can be grouped together, reducing it to 1 restart. | |
| * | |
| * <p>The {@link Driver} class and {@link Driver#drive} are used to run a state machine. | |
| * | |
| * <p>A guide is available at <a href="https://bazel.build/contribute/statemachine-guide"/>. | |
| */ | |
| @FunctionalInterface | |
| public interface StateMachine { | |
| /** A sentinel value returned when a {@code StateMachine} is done. */ | |
| public static final StateMachine DONE = | |
| t -> { | |
| throw new IllegalStateException("Sentinel DONE state should not be executed."); | |
| }; | |
| /** | |
| * Step performs the next computation. | |
| * | |
| * <p>{@link Tasks#lookup} may be used to request {@link SkyKey}s. The next step will not be | |
| * executed until all requested {@link SkyValue}s are available and their associated callbacks | |
| * have been called. Similarly, {@link Tasks#enqueue} can be used to spawn a concurrent | |
| * subcomputation, which must also complete before the next computation step. | |
| * | |
| * <p>Note that recursive decomposition within subtasks is possible and can be used to capture | |
| * fine-grain dependency structures. This is required to correctly model the example in the class | |
| * description. {@code <A1, A2, A3>} and {@code <B1, B2, B3>} become concurrent, multi-step, | |
| * subtasks. | |
| * | |
| * @param tasks an interface for adding subtasks, which may be either {@link SkyKey} lookups or | |
| * child state machines. The {@code tasks} handle is associated with this state machine and | |
| * other state machines should not use it. | |
| * @return an instance indicating the next computation or {@link #DONE} on completion. | |
| */ | |
| StateMachine step(Tasks tasks) throws InterruptedException; | |
| /** | |
| * Tasks allows registering logically parallel subtasks. | |
| * | |
| * <p>Completion of the current step waits until all subtasks are complete. | |
| */ | |
| interface Tasks { | |
| /** | |
| * Enqueues a subtask for logically concurrent evaluation. | |
| * | |
| * <p>The next step will not be executed until the subtask completes. If more than one subtask | |
| * is enqueued, the next step waits on all subtasks. | |
| */ | |
| void enqueue(StateMachine subtask); | |
| /** | |
| * A lookup that handles no exceptions. | |
| * | |
| * <p>This lookup is logically concurrent with other subtasks. The state machine {@link Driver} | |
| * may defer the callback until after a {@code Skyframe} restart if it is not immediately | |
| * available. | |
| * | |
| * <p>Unhandled exceptions eventually set a fail fast condition over the entire state machine | |
| * tree and no further processing occurs afterwards. | |
| * | |
| * <p>IMPLEMENTATION: if an unhandled exception occurs immediately (without a restart) on a | |
| * lookup, {@link Driver} observes unavailability and returns an incomplete status. The driver | |
| * cannot distinguish here between a result that is not yet computed and an unhandled exception. | |
| * | |
| * <p>After a restart, all previously requested values should be available, so observing | |
| * unavailability implies an unhandled exception, triggering fail-fast. | |
| */ | |
| void lookUp(SkyKey key, Consumer<SkyValue> sink); | |
| /** | |
| * A lookup that handles exceptions of the specified type. | |
| * | |
| * <p>The callback could be deferred until the next {@code Skyframe} restart if the queried key | |
| * is not immediately available. | |
| */ | |
| <E extends Exception> void lookUp( | |
| SkyKey key, Class<E> exceptionClass, ValueOrExceptionSink<E> sink); | |
| /** A lookup that handles exceptions of the specified 2 types. */ | |
| <E1 extends Exception, E2 extends Exception> void lookUp( | |
| SkyKey key, | |
| Class<E1> exceptionClass1, | |
| Class<E2> exceptionClass2, | |
| ValueOrException2Sink<E1, E2> sink); | |
| /** A lookup that handles exceptions of the specified 3 types. */ | |
| <E1 extends Exception, E2 extends Exception, E3 extends Exception> void lookUp( | |
| SkyKey key, | |
| Class<E1> exceptionClass1, | |
| Class<E2> exceptionClass2, | |
| Class<E3> exceptionClass3, | |
| ValueOrException3Sink<E1, E2, E3> sink); | |
| } | |
| /** | |
| * Receives the result of a lookup. | |
| * | |
| * <p>Exactly one of {@code value} or {@code exception} will be non-null. | |
| */ | |
| @FunctionalInterface | |
| interface ValueOrExceptionSink<E extends Exception> { | |
| void acceptValueOrException(@Nullable SkyValue value, @Nullable E exception); | |
| } | |
| /** | |
| * Receives the result of a lookup. | |
| * | |
| * <p>Exactly one of {@code value}, {@code e1} or {@code e2} will be non-null. | |
| */ | |
| @FunctionalInterface | |
| interface ValueOrException2Sink<E1 extends Exception, E2 extends Exception> { | |
| void acceptValueOrException2(@Nullable SkyValue value, @Nullable E1 e1, @Nullable E2 e2); | |
| } | |
| /** | |
| * Receives the result of a lookup. | |
| * | |
| * <p>Exactly one of {@code value}, {@code e1}, {@code e2} or {@code e3} will be non-null. | |
| */ | |
| @FunctionalInterface | |
| interface ValueOrException3Sink< | |
| E1 extends Exception, E2 extends Exception, E3 extends Exception> { | |
| void acceptValueOrException3( | |
| @Nullable SkyValue value, @Nullable E1 e1, @Nullable E2 e2, @Nullable E3 e3); | |
| } | |
| } |