Implement basic support for rewinding top-level outputs.

Note that this is all still no-op because nothing overrides the installation of `ImportantOutputHandler#NO_OP`, except in tests.

`ActionRewindStrategy` now offers two public `prepareRewindPlan*` methods: one for lost inputs (called from `ActionExecutionFunction`) and one for lost top-level outputs (called from `CompletionFunction`). They both delegate to the same helper method to compute the rewind graph.

This change only supports regular output files. Notably, tree artifacts and filesets will require further work.

PiperOrigin-RevId: 605428885
Change-Id: Ibd8bfd613eb1e1cecec72ec2d84ff09f2680974f
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
index b9dca2f..3f59158 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionState.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionState.java
@@ -27,6 +27,7 @@
 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;
@@ -201,48 +202,46 @@
    * with a reference to this state will restart, and signals to coalesced shared actions that they
    * should re-evaluate.
    */
-  void obsolete(
-      ActionLookupData requester,
+  synchronized void obsolete(
+      SkyKey requester,
       ConcurrentMap<OwnerlessArtifactWrapper, ActionExecutionState> buildActionMap,
       OwnerlessArtifactWrapper ownerlessArtifactWrapper) {
-    synchronized (this) {
-      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;
-      }
+    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,
-          "removed unexpected state from buildActionMap; requester: %s, this: %s, removed: %s",
-          requester,
+          "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. */