Restrict syscall caching to paths backed by the build's main `FileSystem`.

Avoids wasteful caching of in-memory and transient `FileSystem` types.

PiperOrigin-RevId: 492560997
Change-Id: I19865c33f91566fb101c98508fe8897f9ad115f5
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java b/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java
index e1a6244..f4fcabc 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java
@@ -30,6 +30,7 @@
 import com.google.devtools.build.lib.skyframe.SkyframeExecutorFactory;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutorRepositoryHelpersHolder;
 import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.vfs.SingleFileSystemSyscallCache;
 import com.google.devtools.build.lib.vfs.SyscallCache;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionName;
@@ -96,7 +97,8 @@
   BlazeWorkspace build(
       BlazeRuntime runtime,
       PackageFactory packageFactory,
-      SubscriberExceptionHandler eventBusExceptionHandler) throws AbruptExitException {
+      SubscriberExceptionHandler eventBusExceptionHandler)
+      throws AbruptExitException {
     // Set default values if none are set.
     if (skyframeExecutorFactory == null) {
       skyframeExecutorFactory = new SequencedSkyframeExecutorFactory();
@@ -105,6 +107,9 @@
       perCommandSyscallCache = createPerBuildSyscallCache();
     }
 
+    SingleFileSystemSyscallCache singleFsSyscallCache =
+        new SingleFileSystemSyscallCache(perCommandSyscallCache, runtime.getFileSystem());
+
     SkyframeExecutor skyframeExecutor =
         skyframeExecutorFactory.create(
             packageFactory,
@@ -114,7 +119,7 @@
             workspaceStatusActionFactory,
             diffAwarenessFactories.build(),
             skyFunctions.buildOrThrow(),
-            perCommandSyscallCache,
+            singleFsSyscallCache,
             skyframeExecutorRepositoryHelpersHolder,
             skyKeyStateReceiver == null
                 ? SkyframeExecutor.SkyKeyStateReceiver.NULL_INSTANCE
@@ -128,7 +133,7 @@
         workspaceStatusActionFactory,
         binTools,
         allocationTracker,
-        perCommandSyscallCache);
+        singleFsSyscallCache);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/SingleFileSystemSyscallCache.java b/src/main/java/com/google/devtools/build/lib/vfs/SingleFileSystemSyscallCache.java
new file mode 100644
index 0000000..9befa7f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/vfs/SingleFileSystemSyscallCache.java
@@ -0,0 +1,101 @@
+// Copyright 2022 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.vfs;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.IOException;
+import java.util.Collection;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link SyscallCache} that delegates to a caching implementation only for paths with a
+ * particular {@link FileSystem}.
+ *
+ * <p>Any calls that pass a {@link Path} backed by a different {@link FileSystem} are routed to
+ * {@link SyscallCache#NO_CACHE}. This can be used to ensure that only calls for the build's main
+ * {@link FileSystem} are cached. Common alternative filesystems for which caching is wasteful
+ * include:
+ *
+ * <ul>
+ *   <li>{@link
+ *       com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.BundledFileSystem}, an
+ *       in-memory filesystem (no real filesystem ops to save).
+ *   <li>An {@linkplain com.google.devtools.build.lib.vfs.OutputService.ActionFileSystemType
+ *       action-scoped filesystem} where there is no potential for reuse outside a single action's
+ *       execution, and caching prolongs the lifetime of the instance.
+ */
+public final class SingleFileSystemSyscallCache implements SyscallCache {
+
+  private final SyscallCache delegate;
+  private final FileSystem fs;
+
+  public SingleFileSystemSyscallCache(SyscallCache delegate, FileSystem fs) {
+    this.delegate = checkNotNull(delegate);
+    this.fs = checkNotNull(fs);
+  }
+
+  @Override
+  public Collection<Dirent> readdir(Path path) throws IOException {
+    return delegateFor(path).readdir(path);
+  }
+
+  @Nullable
+  @Override
+  public FileStatus statIfFound(Path path, Symlinks symlinks) throws IOException {
+    return delegateFor(path).statIfFound(path, symlinks);
+  }
+
+  @Nullable
+  @Override
+  public DirentTypeWithSkip getType(Path path, Symlinks symlinks) throws IOException {
+    return delegateFor(path).getType(path, symlinks);
+  }
+
+  @Override
+  public byte[] getFastDigest(Path path) throws IOException {
+    return delegateFor(path).getFastDigest(path);
+  }
+
+  @Override
+  public byte[] getxattr(Path path, String xattrName) throws IOException {
+    return delegateFor(path).getxattr(path, xattrName);
+  }
+
+  @Override
+  public byte[] getxattr(Path path, String xattrName, Symlinks followSymlinks) throws IOException {
+    return delegateFor(path).getxattr(path, xattrName, followSymlinks);
+  }
+
+  @Override
+  public void noteAnalysisPhaseEnded() {
+    delegate.noteAnalysisPhaseEnded();
+  }
+
+  @Override
+  public void clear() {
+    delegate.clear();
+  }
+
+  /** Returns the underlying {@link SyscallCache} used for eligible paths. */
+  // TODO(b/245929310): This shouldn't be exposed.
+  public SyscallCache getUnderlyingCache() {
+    return delegate;
+  }
+
+  private SyscallCache delegateFor(Path path) {
+    return path.getFileSystem().equals(fs) ? delegate : SyscallCache.NO_CACHE;
+  }
+}