|  | // Copyright 2019 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.lib.skyframe; | 
|  |  | 
|  | import static com.google.common.base.Preconditions.checkArgument; | 
|  |  | 
|  | import com.google.common.base.Preconditions; | 
|  | import com.google.common.util.concurrent.SettableFuture; | 
|  | import com.google.devtools.build.lib.actions.Action; | 
|  | import com.google.devtools.build.lib.actions.ActionExecutionException; | 
|  | import com.google.devtools.build.lib.actions.ActionLookupData; | 
|  | import com.google.devtools.build.lib.actions.Artifact.OwnerlessArtifactWrapper; | 
|  | import com.google.devtools.build.lib.actions.LostInputsActionExecutionException; | 
|  | import com.google.devtools.build.lib.actions.SharedActionEvent; | 
|  | import com.google.devtools.build.lib.bugreport.BugReport; | 
|  | import com.google.devtools.build.lib.skyframe.ActionExecutionValue.ActionTransformException; | 
|  | import com.google.devtools.build.skyframe.SkyFunction; | 
|  | import com.google.devtools.build.skyframe.SkyFunction.Environment; | 
|  | import com.google.devtools.build.skyframe.SkyKey; | 
|  | import com.google.errorprone.annotations.DoNotCall; | 
|  | import java.util.concurrent.ConcurrentMap; | 
|  | import javax.annotation.Nullable; | 
|  | import javax.annotation.concurrent.GuardedBy; | 
|  |  | 
|  | /** | 
|  | * A state machine representing the synchronous or asynchronous execution of an action. This is | 
|  | * shared between all instances of the same shared action and must therefore be thread-safe. Note | 
|  | * that only one caller will receive events and output for this action. | 
|  | */ | 
|  | final class ActionExecutionState { | 
|  | /** The owner of this object. Only the owner is allowed to continue work on the state machine. */ | 
|  | private final ActionLookupData actionLookupData; | 
|  |  | 
|  | // Both state and completionFuture may only be read or set when holding the lock for this. The | 
|  | // state machine for these looks like this: | 
|  | // | 
|  | // !state.isDone,completionFuture=null -----> !state.isDone,completionFuture=<value> | 
|  | //                           |                  | | 
|  | //                           |                  | completionFuture.set() | 
|  | //                           v                  v | 
|  | //                    state.isDone,completionFuture=null | 
|  | // | 
|  | // (Also, via obsolete(), all states can transition to state==Obsolete.INSTANCE with a null | 
|  | // completionFuture, which is terminal.) | 
|  | // | 
|  | // No other states are legal. In particular, state.isDone,completionFuture=<value> is not a legal | 
|  | // state. | 
|  |  | 
|  | @GuardedBy("this") | 
|  | private ActionStepOrResult state; | 
|  |  | 
|  | /** | 
|  | * A future to represent action completion of the primary action (randomly picked from the set of | 
|  | * shared actions). This is initially {@code null}, and is only set to a non-null value if this | 
|  | * turns out to be a shared action, and the primary action is not finished yet (i.e., {@code | 
|  | * !state.isDone}. It is non-null while the primary action is being executed, at which point the | 
|  | * thread completing the primary action completes the future, and also sets this field to null. | 
|  | * | 
|  | * <p>The reason for this roundabout approach is to avoid memory allocation if this is not a | 
|  | * shared action, and to release the memory once the action is done. | 
|  | * | 
|  | * <p>Skyframe will attempt to cancel this future if the evaluation is interrupted, which violates | 
|  | * the concurrency assumptions this class makes. Beware! | 
|  | */ | 
|  | @GuardedBy("this") | 
|  | @Nullable | 
|  | private SettableFuture<Void> completionFuture; | 
|  |  | 
|  | ActionExecutionState(ActionLookupData actionLookupData, ActionStepOrResult state) { | 
|  | this.actionLookupData = Preconditions.checkNotNull(actionLookupData); | 
|  | this.state = Preconditions.checkNotNull(state); | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | ActionExecutionValue getResultOrDependOnFuture( | 
|  | SkyFunction.Environment env, | 
|  | ActionLookupData actionLookupData, | 
|  | Action action, | 
|  | SharedActionCallback sharedActionCallback) | 
|  | throws ActionExecutionException, InterruptedException { | 
|  | if (this.actionLookupData.equals(actionLookupData)) { | 
|  | // This object is owned by the Skyframe node executed by the current thread, so we use it to | 
|  | // run the state machine. | 
|  | return runStateMachine(env); | 
|  | } | 
|  |  | 
|  | // This is a shared action, and the primary action is owned by another Skyframe node. If the | 
|  | // primary action is done, we simply return the done value. If this state is obsolete (e.g. | 
|  | // because the other node is rewinding), we restart. Otherwise we register a dependency on the | 
|  | // completionFuture and return null. | 
|  | ActionExecutionValue result; | 
|  | synchronized (this) { | 
|  | if (state == Obsolete.INSTANCE) { | 
|  | scheduleRestart(env); | 
|  | return null; | 
|  | } | 
|  | if (!state.isDone()) { | 
|  | if (completionFuture == null) { | 
|  | completionFuture = SettableFuture.create(); | 
|  | } | 
|  | // We expect to only call this once per shared action; this method should only be called | 
|  | // again after the future is completed. | 
|  | sharedActionCallback.actionStarted(); | 
|  | env.dependOnFuture(completionFuture); | 
|  | if (!env.valuesMissing()) { | 
|  | Preconditions.checkState( | 
|  | completionFuture.isCancelled(), "%s %s", this.actionLookupData, actionLookupData); | 
|  | // The future is unexpectedly done. This must be because it was registered by another | 
|  | // thread earlier and was canceled by Skyframe. We are about to be interrupted ourselves, | 
|  | // but have to do something in the meantime. We can just register a dep with a new future, | 
|  | // then complete it and return. If for some reason this argument is incorrect, we will be | 
|  | // restarted immediately and hopefully have a more consistent result. | 
|  | scheduleRestart(env); | 
|  | } | 
|  | return null; | 
|  | } | 
|  | result = state.get(); | 
|  | } | 
|  | sharedActionCallback.actionCompleted(); | 
|  |  | 
|  | ActionExecutionValue transformed; | 
|  | try { | 
|  | transformed = result.transformForSharedAction(action); | 
|  | } catch (ActionTransformException e) { | 
|  | throw new IllegalStateException( | 
|  | String.format("Cannot share %s and %s", this.actionLookupData, actionLookupData), e); | 
|  | } | 
|  | env.getListener().post(new SharedActionEvent(result, transformed)); | 
|  | return transformed; | 
|  | } | 
|  |  | 
|  | private static void scheduleRestart(Environment env) { | 
|  | SettableFuture<Void> dummyFuture = SettableFuture.create(); | 
|  | env.dependOnFuture(dummyFuture); | 
|  | dummyFuture.set(null); | 
|  | } | 
|  |  | 
|  | @Nullable | 
|  | private ActionExecutionValue runStateMachine(SkyFunction.Environment env) | 
|  | throws ActionExecutionException, InterruptedException { | 
|  | ActionStepOrResult original; | 
|  | synchronized (this) { | 
|  | if (state == Obsolete.INSTANCE) { | 
|  | scheduleRestart(env); | 
|  | return null; | 
|  | } | 
|  | original = state; | 
|  | } | 
|  | ActionStepOrResult current = original; | 
|  | // We do the work _outside_ a synchronized block to avoid blocking threads working on shared | 
|  | // actions that only want to register with the completionFuture. | 
|  | try { | 
|  | while (!current.isDone()) { | 
|  | // Run the state machine for one step; isDone returned false, so this is safe. | 
|  | current = current.run(env); | 
|  |  | 
|  | // This method guarantees that it either blocks until the action is completed and returns | 
|  | // a non-null value, or it registers a dependency with Skyframe and returns null; it must | 
|  | // not return null without registering a dependency, i.e., if {@code !env.valuesMissing()}. | 
|  | if (env.valuesMissing()) { | 
|  | if (current.isDone()) { | 
|  | // This can happen if there was an error in a dep, but another dep was missing. The | 
|  | // Skyframe contract is that this SkyFunction should eagerly process that exception, so | 
|  | // that errors can be transformed in --nokeep_going mode. | 
|  | ActionExecutionValue value = current.get(); | 
|  | BugReport.sendBugReport( | 
|  | new IllegalStateException( | 
|  | actionLookupData + " returned " + value + " with values missing")); | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } | 
|  | } finally { | 
|  | synchronized (this) { | 
|  | if (state != Obsolete.INSTANCE) { | 
|  | Preconditions.checkState(state == original, "Another thread illegally modified state"); | 
|  | state = current; | 
|  | if (current.isDone() && completionFuture != null) { | 
|  | completionFuture.set(null); | 
|  | completionFuture = null; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | // We're done, return the value to the caller (or throw an exception). | 
|  | return current.get(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Removes this state from {@code buildActionMap}, marks it obsolete so that racing shared actions | 
|  | * with a reference to this state will restart, and signals to coalesced shared actions that they | 
|  | * should re-evaluate. | 
|  | */ | 
|  | synchronized void obsolete( | 
|  | SkyKey requester, | 
|  | ConcurrentMap<OwnerlessArtifactWrapper, ActionExecutionState> buildActionMap, | 
|  | OwnerlessArtifactWrapper ownerlessArtifactWrapper) { | 
|  | if (actionLookupData.equals(requester)) { | 
|  | // An action state's owner only obsoletes it when rewinding. The lost inputs exception thrown | 
|  | // from ActionStepOrResult#run left its state undone. | 
|  | Preconditions.checkState( | 
|  | !state.isDone(), "owner unexpectedly obsoleted done state: %s", actionLookupData); | 
|  | ActionExecutionState removedState = buildActionMap.remove(ownerlessArtifactWrapper); | 
|  | Preconditions.checkState( | 
|  | removedState == this, | 
|  | "owner removed unexpected state from buildActionMap; owner: %s, removed: %s", | 
|  | actionLookupData, | 
|  | removedState.actionLookupData); | 
|  | state = Obsolete.INSTANCE; | 
|  | if (completionFuture != null) { | 
|  | completionFuture.set(null); | 
|  | completionFuture = null; | 
|  | } | 
|  | return; | 
|  | } | 
|  | if (!state.isDone()) { | 
|  | // An action obsoletes other actions' states when rewinding its dependencies. It may race with | 
|  | // other actions to do so. Removing the buildActionMap entry must only be done by the race's | 
|  | // winner, to ensure the removal only happens once and removes this state. | 
|  | // | 
|  | // An action may also attempt to obsolete a dependency's not-done state, if it lost the race | 
|  | // with another rewinding action, and the dep started evaluating. If so, then do nothing, | 
|  | // because that dep is already doing what it needs to. | 
|  | return; | 
|  | } | 
|  | ActionExecutionState removedState = buildActionMap.remove(ownerlessArtifactWrapper); | 
|  | Preconditions.checkState( | 
|  | removedState == this, | 
|  | "removed unexpected state from buildActionMap; requester: %s, this: %s, removed: %s", | 
|  | requester, | 
|  | actionLookupData, | 
|  | removedState.actionLookupData); | 
|  | state = Obsolete.INSTANCE; | 
|  | } | 
|  |  | 
|  | /** A callback to receive events for shared actions that are not executed. */ | 
|  | public interface SharedActionCallback { | 
|  | /** Called if the action is shared and the primary action is already executing. */ | 
|  | void actionStarted(); | 
|  |  | 
|  | /** | 
|  | * Called when the primary action is done (on the next call to {@link | 
|  | * #getResultOrDependOnFuture}. | 
|  | */ | 
|  | void actionCompleted(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A state machine where instances of this interface either represent an intermediate state that | 
|  | * requires more work to be done (possibly waiting for a ListenableFuture to complete) or the | 
|  | * final result of the executed action (either an ActionExecutionValue or an Exception). | 
|  | * | 
|  | * <p>This design allows us to store the current state of the in-progress action execution using a | 
|  | * single object reference. | 
|  | * | 
|  | * <p>Do not implement this interface directly! In order to implement an action step, subclass | 
|  | * {@link ActionStep}, and implement {@link #run}. In order to represent a result, use {@link | 
|  | * #of}. | 
|  | */ | 
|  | sealed interface ActionStepOrResult permits ActionStep, Finished, Exceptional, Obsolete { | 
|  | static ActionStepOrResult of(ActionExecutionValue value) { | 
|  | return new Finished(value); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Must not be called with a {@link LostInputsActionExecutionException}. Throw it from {@link | 
|  | * #run} instead. | 
|  | */ | 
|  | static ActionStepOrResult of(ActionExecutionException exception) { | 
|  | checkArgument( | 
|  | !(exception instanceof LostInputsActionExecutionException), | 
|  | "unexpected LostInputs exception: %s", | 
|  | exception); | 
|  | return new Exceptional(exception); | 
|  | } | 
|  |  | 
|  | @DoNotCall("Throw from #run instead.") | 
|  | static ActionStepOrResult of(LostInputsActionExecutionException ignored) { | 
|  | throw new IllegalArgumentException(); | 
|  | } | 
|  |  | 
|  | static ActionStepOrResult of(InterruptedException exception) { | 
|  | return new Exceptional(exception); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns true if and only if the underlying action is complete, i.e., it is legal to call | 
|  | * {@link #get}. The return value of a single object must not change over time. Instead, call | 
|  | * {@link ActionStepOrResult#of} to return a final result (or exception). | 
|  | */ | 
|  | boolean isDone(); | 
|  |  | 
|  | /** | 
|  | * Returns the next state of the state machine after performing some work towards the end goal | 
|  | * of executing the action. This must only be called if {@link #isDone} returns false, and must | 
|  | * only be called by one thread at a time for the same instance. | 
|  | */ | 
|  | ActionStepOrResult run(SkyFunction.Environment env) | 
|  | throws LostInputsActionExecutionException, InterruptedException; | 
|  |  | 
|  | /** | 
|  | * Returns the final value of the action or an exception to indicate that the action failed (or | 
|  | * the process was interrupted). This must only be called if {@link #isDone} returns true. | 
|  | */ | 
|  | ActionExecutionValue get() throws ActionExecutionException, InterruptedException; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Abstract implementation of {@link ActionStepOrResult} that declares final implementations for | 
|  | * {@link #isDone} (to return false) and {@link #get} (to throw {@link IllegalStateException}). | 
|  | * | 
|  | * <p>The framework prevents concurrent calls to {@link #run}, so implementations can keep state | 
|  | * without having to lock. Note that there may be multiple calls to {@link #run} from different | 
|  | * threads, as long as they do not overlap in time. | 
|  | */ | 
|  | abstract static non-sealed class ActionStep implements ActionStepOrResult { | 
|  | @Override | 
|  | public final boolean isDone() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public final ActionExecutionValue get() { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Represents a finished action with a specific value. We specifically avoid anonymous inner | 
|  | * classes to not accidentally retain a reference to the ActionRunner. | 
|  | */ | 
|  | private static final class Finished implements ActionStepOrResult { | 
|  | private final ActionExecutionValue value; | 
|  |  | 
|  | Finished(ActionExecutionValue value) { | 
|  | this.value = value; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isDone() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionStepOrResult run(SkyFunction.Environment env) { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionExecutionValue get() { | 
|  | return value; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Represents a finished action with an exception. We specifically avoid anonymous inner classes | 
|  | * to not accidentally retain a reference to the ActionRunner. | 
|  | */ | 
|  | private static final class Exceptional implements ActionStepOrResult { | 
|  | private final Exception e; | 
|  |  | 
|  | Exceptional(ActionExecutionException e) { | 
|  | this.e = e; | 
|  | } | 
|  |  | 
|  | Exceptional(InterruptedException e) { | 
|  | this.e = e; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean isDone() { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionStepOrResult run(SkyFunction.Environment env) { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionExecutionValue get() throws ActionExecutionException, InterruptedException { | 
|  | if (e instanceof InterruptedException interruptedException) { | 
|  | throw interruptedException; | 
|  | } | 
|  | throw (ActionExecutionException) e; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Represents an action state that is obsolete. Any non-primary shared actions observing this | 
|  | * state must restart (see {@link #scheduleRestart}. | 
|  | */ | 
|  | private static final class Obsolete implements ActionStepOrResult { | 
|  | private static final Obsolete INSTANCE = new Obsolete(); | 
|  |  | 
|  | @Override | 
|  | public boolean isDone() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionStepOrResult run(SkyFunction.Environment env) { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public ActionExecutionValue get() { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  | } | 
|  | } |