Move ActionExecutionState to a top-level class
This is in preparation for merging ActionExecutionFunction and
SkyframeActionExecutor, or at least sharing more infrastructure between
the two.
This should be a no-op.
Progress on #6394.
PiperOrigin-RevId: 234956624
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
index cadc6dc..4ba628b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
@@ -57,7 +57,6 @@
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.rules.cpp.IncludeScannable;
import com.google.devtools.build.lib.skyframe.ActionRewindStrategy.RewindPlan;
-import com.google.devtools.build.lib.skyframe.SkyframeActionExecutor.ActionExecutionState;
import com.google.devtools.build.lib.util.io.FileOutErr;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.FileSystem;
@@ -559,7 +558,8 @@
// In either case, we must use this continuation to continue. Note that in the first case,
// we don't have any input metadata available, so we couldn't re-execute the action even if we
// wanted to.
- return previousAction.getResultOrDependOnFuture(env, actionLookupData, action);
+ return previousAction.getResultOrDependOnFuture(
+ env, actionLookupData, action, skyframeActionExecutor.getActionCompletedReceiver());
}
// The metadataHandler may be recreated if we discover inputs.
ArtifactPathResolver pathResolver = ArtifactPathResolver.createPathResolver(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionState.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionState.java
new file mode 100644
index 0000000..92d6928
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionState.java
@@ -0,0 +1,178 @@
+// 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 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.skyframe.SkyframeActionExecutor.ActionCompletedReceiver;
+import com.google.devtools.build.skyframe.SkyFunction;
+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 {
+ private final ActionLookupData actionLookupData;
+
+ @GuardedBy("this")
+ private ActionStepOrResult state;
+
+ ActionExecutionState(ActionLookupData actionLookupData, ActionStepOrResult state) {
+ this.actionLookupData = actionLookupData;
+ this.state = state;
+ }
+
+ public ActionLookupData getActionLookupData() {
+ return actionLookupData;
+ }
+
+ public ActionExecutionValue getResultOrDependOnFuture(
+ SkyFunction.Environment env,
+ ActionLookupData actionLookupData,
+ Action action,
+ ActionCompletedReceiver actionCompletedReceiver)
+ throws ActionExecutionException, InterruptedException {
+ if (actionLookupData.equals(this.actionLookupData)) {
+ // This continuation 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 executed action is owned by another Skyframe node. We do
+ // not attempt to make progress, but instead block waiting for the owner to complete the
+ // action. This is the same behavior as before this comment was added.
+ //
+ // When we async action execution we MUST also change this to async execution. Otherwise we
+ // can end up with a deadlock where all Skyframe threads are blocked here, and no thread is
+ // available to make progress on the original action.
+ synchronized (this) {
+ while (!state.isDone()) {
+ this.wait();
+ }
+ try {
+ return state.get().transformForSharedAction(action.getOutputs());
+ } finally {
+ if (action.getProgressMessage() != null) {
+ actionCompletedReceiver.actionCompleted(actionLookupData);
+ }
+ }
+ }
+ }
+
+ private synchronized ActionExecutionValue runStateMachine(SkyFunction.Environment env)
+ throws ActionExecutionException, InterruptedException {
+ while (!state.isDone()) {
+ // Run the state machine for one step; isDone returned false, so this is safe.
+ state = state.run(env);
+
+ // This method guarantees that it either blocks until the action is completed, or it
+ // registers a dependency on a ListenableFuture and returns null (it may only return null if
+ // valuesMissing returns true).
+ if (env.valuesMissing()) {
+ return null;
+ }
+ }
+ this.notifyAll();
+ // We're done, return the value to the caller (or throw an exception).
+ return state.get();
+ }
+
+ /**
+ * 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.
+ */
+ interface ActionStepOrResult {
+ static ActionStepOrResult of(ActionExecutionValue value) {
+ return new FinishedActionStepOrResult(value);
+ }
+
+ static ActionStepOrResult of(ActionExecutionException exception) {
+ return new ExceptionalActionStepOrResult(exception);
+ }
+
+ static ActionStepOrResult of(InterruptedException exception) {
+ return new ExceptionalActionStepOrResult(exception);
+ }
+
+ /**
+ * Returns true if and only if the underlying action is complete, i.e., it is legal to call
+ * {@link #get}.
+ */
+ default boolean isDone() {
+ return true;
+ }
+
+ /**
+ * 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.
+ */
+ default ActionStepOrResult run(SkyFunction.Environment env) {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * 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.
+ */
+ default ActionExecutionValue get() throws ActionExecutionException, InterruptedException {
+ 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 FinishedActionStepOrResult implements ActionStepOrResult {
+ private final ActionExecutionValue value;
+
+ FinishedActionStepOrResult(ActionExecutionValue value) {
+ this.value = value;
+ }
+
+ 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 ExceptionalActionStepOrResult implements ActionStepOrResult {
+ private final Exception e;
+
+ ExceptionalActionStepOrResult(ActionExecutionException e) {
+ this.e = e;
+ }
+
+ ExceptionalActionStepOrResult(InterruptedException e) {
+ this.e = e;
+ }
+
+ public ActionExecutionValue get() throws ActionExecutionException, InterruptedException {
+ if (e instanceof InterruptedException) {
+ throw (InterruptedException) e;
+ }
+ throw (ActionExecutionException) e;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
index 6b0422e..65027b1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
@@ -85,6 +85,7 @@
import com.google.devtools.build.lib.profiler.SilentCloseable;
import com.google.devtools.build.lib.rules.cpp.IncludeScannable;
import com.google.devtools.build.lib.runtime.KeepGoingOption;
+import com.google.devtools.build.lib.skyframe.ActionExecutionState.ActionStepOrResult;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.io.FileOutErr;
import com.google.devtools.build.lib.vfs.FileSystem;
@@ -122,6 +123,7 @@
* all output artifacts were created, error reporting, etc.
*/
public final class SkyframeActionExecutor {
+
enum ProgressEventBehavior {
EMIT,
SUPPRESS
@@ -239,6 +241,10 @@
}
}
+ ActionCompletedReceiver getActionCompletedReceiver() {
+ return completionReceiver;
+ }
+
/**
* Return the map of mostly recently executed bad actions to their corresponding exception.
* See {#findAndStoreArtifactConflicts()}.
@@ -550,12 +556,15 @@
new OwnerlessArtifactWrapper(action.getPrimaryOutput()),
(_unused_key) ->
new ActionExecutionState(
- action,
- metadataHandler,
- actionStartTime,
- actionExecutionContext,
- actionLookupData));
- return activeAction.getResultOrDependOnFuture(env, actionLookupData, action);
+ actionLookupData,
+ new ActionRunner(
+ action,
+ metadataHandler,
+ actionStartTime,
+ actionExecutionContext,
+ actionLookupData)));
+ return activeAction.getResultOrDependOnFuture(
+ env, actionLookupData, action, completionReceiver);
}
/**
@@ -781,153 +790,6 @@
this.actionInputPrefetcher = actionInputPrefetcher;
}
- /**
- * 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 {
- private final ActionLookupData actionLookupData;
- private ActionStepOrResult state;
-
- ActionExecutionState(
- Action action,
- ActionMetadataHandler metadataHandler,
- long actionStartTime,
- ActionExecutionContext actionExecutionContext,
- ActionLookupData actionLookupData) {
- this.actionLookupData = actionLookupData;
- this.state =
- new ActionRunner(
- action, metadataHandler, actionStartTime, actionExecutionContext, actionLookupData);
- }
-
- public ActionLookupData getActionLookupData() {
- return actionLookupData;
- }
-
- public ActionExecutionValue getResultOrDependOnFuture(
- SkyFunction.Environment env, ActionLookupData actionLookupData, Action action)
- throws ActionExecutionException, InterruptedException {
- if (actionLookupData.equals(this.actionLookupData)) {
- // This continuation 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 executed action is owned by another Skyframe node. We do
- // not attempt to make progress, but instead block waiting for the owner to complete the
- // action. This is the same behavior as before this comment was added.
- //
- // When we async action execution we MUST also change this to async execution. Otherwise we
- // can end up with a deadlock where all Skyframe threads are blocked here, and no thread is
- // available to make progress on the original action.
- synchronized (this) {
- while (!state.isDone()) {
- this.wait();
- }
- try {
- return state.get().transformForSharedAction(action.getOutputs());
- } finally {
- if (action.getProgressMessage() != null) {
- completionReceiver.actionCompleted(actionLookupData);
- }
- }
- }
- }
-
- private synchronized ActionExecutionValue runStateMachine(SkyFunction.Environment env)
- throws ActionExecutionException, InterruptedException {
- while (!state.isDone()) {
- // Run the state machine for one step; isDone returned false, so this is safe.
- state = state.run(env);
-
- // This method guarantees that it either blocks until the action is completed, or it
- // registers a dependency on a ListenableFuture and returns null (it may only return null if
- // valuesMissing returns true).
- if (env.valuesMissing()) {
- return null;
- }
- }
- this.notifyAll();
- // We're done, return the value to the caller (or throw an exception).
- return state.get();
- }
- }
-
- /**
- * 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.
- */
- private interface ActionStepOrResult {
- /**
- * Returns true if and only if the underlying action is complete, i.e., it is legal to call
- * {@link #get}.
- */
- default boolean isDone() {
- return true;
- }
-
- /**
- * 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.
- */
- default ActionStepOrResult run(SkyFunction.Environment env) {
- throw new IllegalStateException();
- }
-
- /**
- * 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.
- */
- default ActionExecutionValue get() throws ActionExecutionException, InterruptedException {
- 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 FinishedActionStepOrResult implements ActionStepOrResult {
- private final ActionExecutionValue value;
-
- FinishedActionStepOrResult(ActionExecutionValue value) {
- this.value = value;
- }
-
- 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 ExceptionalActionStepOrResult implements ActionStepOrResult {
- private final Exception e;
-
- ExceptionalActionStepOrResult(ActionExecutionException e) {
- this.e = e;
- }
-
- ExceptionalActionStepOrResult(InterruptedException e) {
- this.e = e;
- }
-
- public ActionExecutionValue get() throws ActionExecutionException, InterruptedException {
- if (e instanceof InterruptedException) {
- throw (InterruptedException) e;
- }
- throw (ActionExecutionException) e;
- }
- }
-
/** A local interface to unify immediate and deferred action execution code paths. */
private interface ActionClosure {
ActionResult execute() throws ActionExecutionException, InterruptedException;
@@ -1021,7 +883,7 @@
// This try-catch block cannot trigger rewinding, so it is safe to notify the status
// reporter and also post the ActionCompletionEvent.
notifyActionCompletion(env.getListener(), /*postActionCompletionEvent=*/ true);
- return new ExceptionalActionStepOrResult(e);
+ return ActionStepOrResult.of(e);
}
// This is the first iteration of the async action execution framework. It is currently only
@@ -1036,13 +898,13 @@
try {
futureSpawn = spawnAction.execMaybeAsync(actionExecutionContext);
} catch (InterruptedException e) {
- return new ExceptionalActionStepOrResult(e);
+ return ActionStepOrResult.of(e);
} catch (ActionExecutionException e) {
- return new ExceptionalActionStepOrResult(e);
+ return ActionStepOrResult.of(e);
}
return new ActionCompletionStep(futureSpawn, spawnAction);
} catch (ExecException e) {
- return new ExceptionalActionStepOrResult(e.toActionExecutionException(spawnAction));
+ return ActionStepOrResult.of(e.toActionExecutionException(spawnAction));
}
}
@@ -1111,11 +973,11 @@
lostInputsActionExecutionException = e;
throw lostInputsActionExecutionException;
} catch (InterruptedException e) {
- return new ExceptionalActionStepOrResult(e);
+ return ActionStepOrResult.of(e);
}
- return new FinishedActionStepOrResult(actuallyCompleteAction(eventHandler, actionResult));
+ return ActionStepOrResult.of(actuallyCompleteAction(eventHandler, actionResult));
} catch (ActionExecutionException e) {
- return new ExceptionalActionStepOrResult(e);
+ return ActionStepOrResult.of(e);
} finally {
notifyActionCompletion(
eventHandler,