Implement a MinimalOutputStore that caches less data.

There may be room for further reduction of storage, however it would require more invasive changes to consumers and potentially ActionFS.

RELNOTES: None.
PiperOrigin-RevId: 214833898
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java b/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
index 416b2a6..deaaf79 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java
@@ -111,6 +111,11 @@
     return 0;
   }
 
+  /** Returns {@code true} if this is a special marker as opposed to a representing a real file. */
+  public final boolean isMarkerValue() {
+    return this instanceof Singleton;
+  }
+
   /**
    * Provides a best-effort determination whether the file was changed since the digest was
    * computed. This method performs file system I/O, so may be expensive. It's primarily intended to
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 ebc574b..e21d8db 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
@@ -67,6 +67,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
 import javax.annotation.Nullable;
 
 /**
@@ -379,9 +380,18 @@
           actionLookupData,
           /* inputDiscoveryRan= */ false);
     }
-    // This may be recreated if we discover inputs.
-    ActionMetadataHandler metadataHandler = new ActionMetadataHandler(state.inputArtifactData,
-        action.getOutputs(), tsgm.get(), pathResolver(state.actionFileSystem));
+    // The metadataHandler may be recreated (via the supplier) if we discover inputs.
+    ArtifactPathResolver pathResolver = ArtifactPathResolver.createPathResolver(
+        state.actionFileSystem, skyframeActionExecutor.getExecRoot());
+    Supplier<ActionMetadataHandler> metadataHandlerSupplier =
+        () ->
+            new ActionMetadataHandler(
+                state.inputArtifactData,
+                action.getOutputs(),
+                tsgm.get(),
+                pathResolver,
+                state.actionFileSystem == null ? new OutputStore() : new MinimalOutputStore());
+    ActionMetadataHandler metadataHandler = metadataHandlerSupplier.get();
     long actionStartTime = BlazeClock.nanoTime();
     // We only need to check the action cache if we haven't done it on a previous run.
     if (!state.hasCheckedActionCache()) {
@@ -452,9 +462,7 @@
       perActionFileCache =
           new PerActionFileCache(state.inputArtifactData, /*missingArtifactsAllowed=*/ false);
 
-      metadataHandler =
-          new ActionMetadataHandler(state.inputArtifactData, action.getOutputs(), tsgm.get(),
-              pathResolver(state.actionFileSystem));
+      metadataHandler = metadataHandlerSupplier.get();
       // Set the MetadataHandler to accept output information.
       metadataHandler.discardOutputMetadata();
     }
@@ -534,9 +542,7 @@
         // the documentation on MetadataHandler.artifactOmitted. This works by accident because
         // markOmitted is only called for remote execution, and this code only gets executed for
         // local execution.
-        metadataHandler =
-            new ActionMetadataHandler(state.inputArtifactData, action.getOutputs(), tsgm.get(),
-                pathResolver(state.actionFileSystem));
+        metadataHandler = metadataHandlerSupplier.get();
       }
     }
     Preconditions.checkState(!env.valuesMissing(), action);
@@ -545,11 +551,6 @@
     return state.value;
   }
 
-  private ArtifactPathResolver pathResolver(@Nullable FileSystem actionFileSystem) {
-    return ArtifactPathResolver.createPathResolver(
-        actionFileSystem, skyframeActionExecutor.getExecRoot());
-  }
-
   private static final Function<Artifact, SkyKey> TO_NONMANDATORY_SKYKEY =
       new Function<Artifact, SkyKey>() {
         @Nullable
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
index 163dcf1..e62aaff 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionValue.java
@@ -49,11 +49,10 @@
  *
  * <p>Concerning the data in this class:
  *
- * <p>We want to track all output data from an ActionExecutionValue. See {@link
- * ActionMetadataHandler} for a discussion of how its fields {@link
- * ActionMetadataHandler#outputArtifactData} and {@link ActionMetadataHandler#additionalOutputData}
- * relate. The relationship is the same between {@link #artifactData} and {@link
- * #additionalOutputData} here.
+ * <p>We want to track all output data from an ActionExecutionValue. See {@link OutputStore} for a
+ * discussion of how its fields {@link OutputStore#artifactData} and {@link
+ * OutputStore#additionalOutputData} relate. The relationship is the same between {@link
+ * #artifactData} and {@link #additionalOutputData} here.
  */
 @Immutable
 @ThreadSafe
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
index c41c893..79bde1c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
@@ -99,19 +99,20 @@
    */
   private final AtomicBoolean executionMode = new AtomicBoolean(false);
 
-  // TODO(b/115361150): Swap in a different output store when using ActionFS.
-  private final OutputStore store = new OutputStore();
+  private final OutputStore store;
 
   @VisibleForTesting
   public ActionMetadataHandler(
       ActionInputMap inputArtifactData,
       Iterable<Artifact> outputs,
       TimestampGranularityMonitor tsgm,
-      ArtifactPathResolver artifactPathResolver)  {
+      ArtifactPathResolver artifactPathResolver,
+      OutputStore store)  {
     this.inputArtifactData = Preconditions.checkNotNull(inputArtifactData);
     this.outputs = ImmutableSet.copyOf(outputs);
     this.tsgm = tsgm;
     this.artifactPathResolver = artifactPathResolver;
+    this.store = store;
   }
 
   /**
@@ -322,10 +323,8 @@
         }
 
         // A minor hack: maybeStoreAdditionalData will force the data to be stored via
-        // store.putAdditionalOutputData.
-        maybeStoreAdditionalData(treeFileArtifact, fileMetadata, null);
-        cachedValue = Preconditions.checkNotNull(
-            store.getAdditionalOutputData(treeFileArtifact), treeFileArtifact);
+        // store.putAdditionalOutputData, if the underlying OutputStore supports it.
+        cachedValue = maybeStoreAdditionalData(treeFileArtifact, fileMetadata, null);
       }
 
       values.put(treeFileArtifact, cachedValue);
@@ -363,7 +362,8 @@
 
   @Override
   public Iterable<TreeFileArtifact> getExpandedOutputs(Artifact artifact) {
-    return ImmutableSet.copyOf(store.getOrCreateTreeArtifactContents(artifact));
+    Set<TreeFileArtifact> contents = store.getTreeArtifactContents(artifact);
+    return contents != null ? ImmutableSet.copyOf(contents) : ImmutableSet.of();
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/MinimalOutputStore.java b/src/main/java/com/google/devtools/build/lib/skyframe/MinimalOutputStore.java
new file mode 100644
index 0000000..714bc24
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/MinimalOutputStore.java
@@ -0,0 +1,55 @@
+// Copyright 2018 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.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
+import com.google.devtools.build.lib.actions.ArtifactFileMetadata;
+import com.google.devtools.build.lib.actions.FileArtifactValue;
+
+/**
+ * Implementation of {@link OutputStore} that retains a minimal amount of output data necessary to
+ * complete the build.
+ *
+ * <p>This store is intended for use with in-memory file systems, where aggressive caching would not
+ * be worthwhile.
+ */
+// TODO(b/115361150): See if even less data can be retained. Specifically, we shouldn't need to
+// call injectRemoteFile for anything, including children of tree artifacts.
+final class MinimalOutputStore extends OutputStore {
+
+  @Override
+  void putAdditionalOutputData(Artifact artifact, FileArtifactValue value) {
+    if (value.isMarkerValue()) {
+      super.putAdditionalOutputData(artifact, value);
+    }
+  }
+
+  @Override
+  void putArtifactData(Artifact artifact, ArtifactFileMetadata value) {}
+
+  @Override
+  void addTreeArtifactContents(Artifact artifact, TreeFileArtifact contents) {}
+
+  @Override
+  void injectRemoteFile(Artifact output, byte[] digest, long size, int locationIndex) {
+    if (isChildOfTreeArtifact(output)) {
+      super.injectRemoteFile(output, digest, size, locationIndex);
+    }
+  }
+
+  private static boolean isChildOfTreeArtifact(Artifact artifact) {
+    return artifact.hasParent() && artifact.getParent().isTreeArtifact();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java b/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
index 95c34df..fcff7ae 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
@@ -146,17 +146,9 @@
     return treeArtifactContents.get(artifact);
   }
 
-  /**
-   * Same as {@link #getTreeArtifactContents} except that a new set of contents is created and
-   * stored instead of returning {@code null}.
-   */
-  Set<TreeFileArtifact> getOrCreateTreeArtifactContents(Artifact artifact) {
-    Preconditions.checkArgument(artifact.isTreeArtifact(), artifact);
-    return treeArtifactContents.computeIfAbsent(artifact, unused -> Sets.newConcurrentHashSet());
-  }
-
   void addTreeArtifactContents(Artifact artifact, TreeFileArtifact contents) {
-    getOrCreateTreeArtifactContents(artifact).add(contents);
+    Preconditions.checkArgument(artifact.isTreeArtifact(), artifact);
+    treeArtifactContents.computeIfAbsent(artifact, a -> Sets.newConcurrentHashSet()).add(contents);
   }
 
   void injectRemoteFile(Artifact output, byte[] digest, long size, int locationIndex) {
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FileArtifactValueTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FileArtifactValueTest.java
index 1e85b6c..fbad99e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FileArtifactValueTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FileArtifactValueTest.java
@@ -229,4 +229,17 @@
     clock.advanceMillis(1);
     assertThat(value.wasModifiedSinceDigest(path)).isTrue();
   }
+
+  @Test
+  public void testIsMarkerValue_marker() {
+    assertThat(FileArtifactValue.DEFAULT_MIDDLEMAN.isMarkerValue()).isTrue();
+    assertThat(FileArtifactValue.MISSING_FILE_MARKER.isMarkerValue()).isTrue();
+    assertThat(FileArtifactValue.OMITTED_FILE_MARKER.isMarkerValue()).isTrue();
+  }
+
+  @Test
+  public void testIsMarkerValue_notMarker() throws Exception {
+    FileArtifactValue value = create(scratchFile("/dir/artifact1", 0L, "content"));
+    assertThat(value.isMarkerValue()).isFalse();
+  }
 }