code: @Beta // TODO(kevinb): decide best return type
    public static List<Throwable> getCausalChain(Throwable throwable) {
      checkNotNull(throwable);
      List<Throwable> causes = new ArrayList<>(4);
      causes.add(throwable);
  
      // Keep a second pointer that slowly walks the causal chain. If the fast pointer ever catches
      // the slower pointer, then there's a loop.
      Throwable slowPointer = throwable;
      boolean advanceSlowPointer = false;
  
      Throwable cause;
      while ((cause = throwable.getCause()) != null) {
        throwable = cause;
        causes.add(throwable);
  
        if (throwable == slowPointer) {
          throw new IllegalArgumentException("Loop in causal chain detected.", throwable);
        }
        if (advanceSlowPointer) {
          slowPointer = slowPointer.getCause();
        }
        advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration
      }
      return Collections.unmodifiableList(causes);
    }
docs: Gets a {@code Throwable} cause chain as a list. The first entry in the list will be {@code
  throwable} followed by its cause hierarchy. Note that this is a snapshot of the cause chain and
  will not reflect any subsequent changes to the cause chain.
  
  <p>Here's an example of how it can be used to find specific types of exceptions in the cause
  chain:
  
  <pre>
  Iterables.filter(Throwables.getCausalChain(e), IOException.class));
  </pre>
  
  @param throwable the non-null {@code Throwable} to extract causes from
  @return an unmodifiable list containing the cause chain starting with {@code throwable}
  @throws IllegalArgumentException if there is a loop in the causal chain