Remove WalkableGraph#exists and allow WalkableGraph#getValue and WalkableGraph#getException to be given non-existent keys without crashing. Add WalkableGraph#isCycle to fill the gap in testing for the difference between non-existence and depending on a cycle.

--
PiperOrigin-RevId: 143205289
MOS_MIGRATED_REVID=143205289
diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
index ae7aa9d..551b098 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
@@ -43,6 +43,7 @@
 import com.google.devtools.build.lib.graph.Digraph;
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.DependencyFilter;
+import com.google.devtools.build.lib.packages.NoSuchPackageException;
 import com.google.devtools.build.lib.packages.NoSuchTargetException;
 import com.google.devtools.build.lib.packages.NoSuchThingException;
 import com.google.devtools.build.lib.packages.Package;
@@ -728,9 +729,6 @@
   public Target getTarget(Label label)
       throws TargetNotFoundException, QueryException, InterruptedException {
     SkyKey packageKey = PackageValue.key(label.getPackageIdentifier());
-    if (!graph.exists(packageKey)) {
-      throw new QueryException(packageKey + " does not exist in graph");
-    }
     try {
       PackageValue packageValue = (PackageValue) graph.getValue(packageKey);
       if (packageValue != null) {
@@ -740,8 +738,16 @@
         }
         return packageValue.getPackage().getTarget(label.getName());
       } else {
-        throw (NoSuchThingException) Preconditions.checkNotNull(
-            graph.getException(packageKey), label);
+        NoSuchThingException exception = (NoSuchThingException) graph.getException(packageKey);
+        if (exception != null) {
+          throw exception;
+        }
+        if (graph.isCycle(packageKey)) {
+          throw new NoSuchPackageException(
+              label.getPackageIdentifier(), "Package depends on a cycle");
+        } else {
+          throw new QueryException(packageKey + " does not exist in graph");
+        }
       }
     } catch (NoSuchThingException e) {
       throw new TargetNotFoundException(e);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
index 9a94f3f..6fef55c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
@@ -135,7 +135,7 @@
     @Override
     @Nullable
     public SkyValue get(SkyKey key) throws InterruptedException {
-      return walkableGraph.exists(key) ? walkableGraph.getValue(key) : null;
+      return walkableGraph.getValue(key);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java b/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java
index 5e31b91..337b14d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GraphBackedRecursivePackageProvider.java
@@ -81,22 +81,21 @@
       throws NoSuchPackageException, InterruptedException {
     SkyKey pkgKey = PackageValue.key(packageName);
 
-    PackageValue pkgValue;
-    if (graph.exists(pkgKey)) {
-      pkgValue = (PackageValue) graph.getValue(pkgKey);
-      if (pkgValue == null) {
-        NoSuchPackageException nspe = (NoSuchPackageException) graph.getException(pkgKey);
-        if (nspe != null) {
-          throw nspe;
-        }
-        throw new NoSuchPackageException(packageName, "Package depends on a cycle");
-      }
+    PackageValue pkgValue = (PackageValue) graph.getValue(pkgKey);
+    if (pkgValue != null) {
+      return pkgValue.getPackage();
+    }
+    NoSuchPackageException nspe = (NoSuchPackageException) graph.getException(pkgKey);
+    if (nspe != null) {
+      throw nspe;
+    }
+    if (graph.isCycle(pkgKey)) {
+      throw new NoSuchPackageException(packageName, "Package depends on a cycle");
     } else {
       // If the package key does not exist in the graph, then it must not correspond to any package,
       // because the SkyQuery environment has already loaded the universe.
       throw new BuildFileNotFoundException(packageName, "BUILD file not found on package path");
     }
-    return pkgValue.getPackage();
   }
 
   @Override
@@ -139,22 +138,25 @@
   public boolean isPackage(EventHandler eventHandler, PackageIdentifier packageName)
       throws InterruptedException {
     SkyKey packageLookupKey = PackageLookupValue.key(packageName);
-    if (!graph.exists(packageLookupKey)) {
-      // If the package lookup key does not exist in the graph, then it must not correspond to any
-      // package, because the SkyQuery environment has already loaded the universe.
-      return false;
-    }
     PackageLookupValue packageLookupValue = (PackageLookupValue) graph.getValue(packageLookupKey);
     if (packageLookupValue == null) {
-      Exception exception = Preconditions.checkNotNull(graph.getException(packageLookupKey),
-          "During package lookup for '%s', got null for exception", packageName);
-      if (exception instanceof NoSuchPackageException
-          || exception instanceof InconsistentFilesystemException) {
-        eventHandler.handle(Event.error(exception.getMessage()));
+      // Package lookups can't depend on Skyframe cycles.
+      Preconditions.checkState(!graph.isCycle(packageLookupKey), packageLookupKey);
+      Exception exception = graph.getException(packageLookupKey);
+      if (exception == null) {
+        // If the package lookup key does not exist in the graph, then it must not correspond to any
+        // package, because the SkyQuery environment has already loaded the universe.
         return false;
       } else {
-        throw new IllegalStateException("During package lookup for '" + packageName
-            + "', got unexpected exception type", exception);
+        if (exception instanceof NoSuchPackageException
+            || exception instanceof InconsistentFilesystemException) {
+          eventHandler.handle(Event.error(exception.getMessage()));
+          return false;
+        } else {
+          throw new IllegalStateException(
+              "During package lookup for '" + packageName + "', got unexpected exception type",
+              exception);
+        }
       }
     }
     return packageLookupValue.packageExists();
@@ -190,7 +192,7 @@
       roots.addAll(pkgPath.getPathEntries());
     } else {
       RepositoryDirectoryValue repositoryValue =
-            (RepositoryDirectoryValue) graph.getValue(RepositoryDirectoryValue.key(repository));
+          (RepositoryDirectoryValue) graph.getValue(RepositoryDirectoryValue.key(repository));
       if (repositoryValue == null) {
         // If this key doesn't exist, the repository is outside the universe, so we return
         // "nothing".
diff --git a/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java b/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java
index 27475db..8abf5de 100644
--- a/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/DelegatingWalkableGraph.java
@@ -32,26 +32,18 @@
     this.graph = graph;
   }
 
+  @Nullable
   private NodeEntry getEntryForValue(SkyKey key) throws InterruptedException {
     NodeEntry entry =
-        Preconditions.checkNotNull(
-            graph.getBatch(null, Reason.WALKABLE_GRAPH_VALUE, ImmutableList.of(key)).get(key),
-            key);
-    Preconditions.checkState(entry.isDone(), "%s %s", key, entry);
-    return entry;
-  }
-
-  @Override
-  public boolean exists(SkyKey key) throws InterruptedException {
-    NodeEntry entry =
-        graph.getBatch(null, Reason.EXISTENCE_CHECKING, ImmutableList.of(key)).get(key);
-    return entry != null && entry.isDone();
+        graph.getBatch(null, Reason.WALKABLE_GRAPH_VALUE, ImmutableList.of(key)).get(key);
+    return entry != null && entry.isDone() ? entry : null;
   }
 
   @Nullable
   @Override
   public SkyValue getValue(SkyKey key) throws InterruptedException {
-    return getEntryForValue(key).getValue();
+    NodeEntry entry = getEntryForValue(key);
+    return entry == null ? null : entry.getValue();
   }
 
   private static SkyValue getValue(NodeEntry entry) throws InterruptedException {
@@ -93,10 +85,24 @@
     return result;
   }
 
+  @Override
+  public boolean isCycle(SkyKey key) throws InterruptedException {
+    NodeEntry entry = getEntryForValue(key);
+    if (entry == null) {
+      return false;
+    }
+    ErrorInfo errorInfo = entry.getErrorInfo();
+    return errorInfo != null && errorInfo.getCycleInfo() != null;
+  }
+
   @Nullable
   @Override
   public Exception getException(SkyKey key) throws InterruptedException {
-    ErrorInfo errorInfo = getEntryForValue(key).getErrorInfo();
+    NodeEntry entry = getEntryForValue(key);
+    if (entry == null) {
+      return null;
+    }
+    ErrorInfo errorInfo = entry.getErrorInfo();
     return errorInfo == null ? null : errorInfo.getException();
   }
 
diff --git a/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java b/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java
index 3d7796b..8f3dd04 100644
--- a/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java
+++ b/src/main/java/com/google/devtools/build/skyframe/WalkableGraph.java
@@ -30,17 +30,17 @@
  */
 @ThreadSafe
 public interface WalkableGraph {
-
-  /**
-   * Returns whether the given key exists as a done node in the graph. If there is a chance that the
-   * given node does not exist, this method should be called before any others, since the others
-   * throw a {@link RuntimeException} on failure to access a node.
-   */
-  boolean exists(SkyKey key) throws InterruptedException;
-
   /**
    * Returns the value of the given key, or {@code null} if it has no value due to an error during
-   * its computation. A node with this key must exist in the graph.
+   * its computation or it is not done in the graph.
+   *
+   * <p>A node that is done in the graph must have either a non-null getValue, a non-null {@link
+   * #getException}, or a true {@link #isCycle}.
+   *
+   * <p>These three methods should all be reading the same {@link
+   * NodeEntry#getValueMaybeWithMetadata} value internally, so once that value is indirectly
+   * retrieved via one of these methods, the others can read it for free. This is relevant for graph
+   * implementations that may throw an {@link InterruptedException} on retrieving entries and value.
    */
   @Nullable
   SkyValue getValue(SkyKey key) throws InterruptedException;
@@ -63,21 +63,27 @@
 
   /**
    * Returns the exception thrown when computing the node with the given key, if any. If the node
-   * was computed successfully, returns null. A node with this key must exist and be done in the
-   * graph.
+   * was computed successfully, depends on a cycle without any other error, or is not done in the
+   * graph, returns null.
    */
   @Nullable
   Exception getException(SkyKey key) throws InterruptedException;
 
   /**
+   * Returns true if the node with the given {@code key} depends on a cycle. Returns false if the
+   * node does not depend on a cycle, or is not done in the graph.
+   */
+  boolean isCycle(SkyKey key) throws InterruptedException;
+
+  /**
    * Returns a map giving the direct dependencies of the nodes with the given keys. A node for each
-   * given key must exist and be done in the graph.
+   * given key must be done in the graph if it exists.
    */
   Map<SkyKey, Iterable<SkyKey>> getDirectDeps(Iterable<SkyKey> keys) throws InterruptedException;
 
   /**
    * Returns a map giving the reverse dependencies of the nodes with the given keys. A node for each
-   * given key must exist and be done in the graph.
+   * given key must be done in the graph if it exists.
    */
   Map<SkyKey, Iterable<SkyKey>> getReverseDeps(Iterable<SkyKey> keys) throws InterruptedException;