Support lost input detection in action filesystems

An action filesystem may know when an action's inputs have been lost.
If so, and if any have, then suppress the result of that action's
execution and allow action rewinding to try to restore what was lost.

RELNOTES: None.
PiperOrigin-RevId: 281083357
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java
index 7f04336..986b741 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionExecutionContext.java
@@ -185,6 +185,11 @@
         : executor.getExecRoot();
   }
 
+  @Nullable
+  public FileSystem getActionFileSystem() {
+    return actionFileSystem;
+  }
+
   /**
    * Returns the path for an ActionInput.
    *
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 9a370f6..f5ff6e2 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
@@ -782,8 +782,27 @@
     }
     actionExecutionContext.getEventHandler().post(new ScanningActionEvent(action));
     try {
-      return action.discoverInputs(actionExecutionContext);
+      Iterable<Artifact> artifacts = action.discoverInputs(actionExecutionContext);
+
+      // Input discovery may have been affected by lost inputs. If an action filesystem is used, it
+      // may know whether inputs were lost. We should fail fast if any were; rewinding may be able
+      // to fix it.
+      checkActionFileSystemForLostInputs(actionExecutionContext, action, outputService);
+
+      return artifacts;
     } catch (ActionExecutionException e) {
+
+      // Input discovery failures may be caused by lost inputs. Lost input failures have higher
+      // priority because rewinding may be able to restore what was lost and allow the action to
+      // complete without error.
+      if (!(e instanceof LostInputsActionExecutionException)) {
+        try {
+          checkActionFileSystemForLostInputs(actionExecutionContext, action, outputService);
+        } catch (LostInputsActionExecutionException lostInputsException) {
+          e = lostInputsException;
+        }
+      }
+
       if (e instanceof LostInputsActionExecutionException) {
         // If inputs were lost during input discovery, then enrich the exception, informing action
         // rewinding machinery that these lost inputs are now Skyframe deps of the action.
@@ -1019,7 +1038,24 @@
       ActionContinuationOrResult nextActionContinuationOrResult;
       try (SilentCloseable c = profiler.profile(ProfilerTask.INFO, "ActionContinuation.execute")) {
         nextActionContinuationOrResult = actionContinuation.execute();
+
+        // An action's result (or intermediate state) may have been affected by lost inputs. If an
+        // action filesystem is used, it may know whether inputs were lost. We should fail fast if
+        // any were; rewinding may be able to fix it.
+        checkActionFileSystemForLostInputs(actionExecutionContext, action, outputService);
       } catch (ActionExecutionException e) {
+
+        // Action failures may be caused by lost inputs. Lost input failures have higher priority
+        // because rewinding may be able to restore what was lost and allow the action to complete
+        // without error.
+        if (!(e instanceof LostInputsActionExecutionException)) {
+          try {
+            checkActionFileSystemForLostInputs(actionExecutionContext, action, outputService);
+          } catch (LostInputsActionExecutionException lostInputsException) {
+            e = lostInputsException;
+          }
+        }
+
         boolean isLostInputsException = e instanceof LostInputsActionExecutionException;
         if (isLostInputsException) {
           ((LostInputsActionExecutionException) e).setActionStartedEventAlreadyEmitted();
@@ -1381,6 +1417,20 @@
   }
 
   /**
+   * Validates that all action input contents were not lost if they were read, and if an action file
+   * system was used. Throws a {@link LostInputsActionExecutionException} describing the lost inputs
+   * if any were.
+   */
+  private static void checkActionFileSystemForLostInputs(
+      ActionExecutionContext context, Action action, OutputService outputService)
+      throws LostInputsActionExecutionException {
+    FileSystem actionFileSystem = context.getActionFileSystem();
+    if (actionFileSystem != null) {
+      outputService.checkActionFileSystemForLostInputs(actionFileSystem, action);
+    }
+  }
+
+  /**
    * Validates that all action outputs were created or intentionally omitted. This can result in
    * chmod calls on the output files; see {@link ActionMetadataHandler}.
    *
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
index 4420ad6..1fa759f 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
@@ -25,6 +25,7 @@
 import com.google.devtools.build.lib.actions.EnvironmentalExecException;
 import com.google.devtools.build.lib.actions.ExecException;
 import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
+import com.google.devtools.build.lib.actions.LostInputsActionExecutionException;
 import com.google.devtools.build.lib.actions.MetadataConsumer;
 import com.google.devtools.build.lib.actions.cache.MetadataHandler;
 import com.google.devtools.build.lib.events.EventHandler;
@@ -181,6 +182,13 @@
       ImmutableMap<Artifact, ImmutableList<FilesetOutputSymlink>> filesets)
       throws IOException {}
 
+  /**
+   * Checks the filesystem returned by {@link #createActionFileSystem} for errors attributable to
+   * lost inputs.
+   */
+  default void checkActionFileSystemForLostInputs(FileSystem actionFileSystem, Action action)
+      throws LostInputsActionExecutionException {}
+
   default boolean supportsPathResolverForArtifactValues() {
     return false;
   }