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/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;