aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/skyframe/SkyFunctionException.java
blob: c967daf57b79d53c07c0c761fd5eff082f5d5770 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright 2014 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.skyframe;

import com.google.common.base.Preconditions;
import javax.annotation.Nullable;

/**
 * Base class of exceptions thrown by {@link SkyFunction#compute} on failure.
 *
 * SkyFunctions should declare a subclass {@code C} of {@link SkyFunctionException} whose
 * constructors forward fine-grained exception types (e.g. {@code IOException}) to
 * {@link SkyFunctionException}'s constructor, and they should also declare
 * {@link SkyFunction#compute} to throw {@code C}. This way the type system checks that no
 * unexpected exceptions are thrown by the {@link SkyFunction}.
 *
 * <p>We took this approach over using a generic exception class since Java disallows it because of
 * type erasure
 * (see http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#cannotCatch).
 *
 * <p> Note that there are restrictions on what Exception types are allowed to be wrapped in this
 * manner. See {@link SkyFunctionException#validateExceptionType}.
 *
 * <p>Failures are explicitly either transient or persistent. The transience of the failure from
 * {@link SkyFunction#compute} should be influenced only by the computations done, and not by the
 * transience of the failures from computations requested via
 * {@link SkyFunction.Environment#getValueOrThrow}.
 */
public abstract class SkyFunctionException extends Exception {

  /** The transience of the error. */
  public enum Transience {
    /**
     * An error that may or may not occur again if the computation were re-run. If a computation
     * results in a transient error and is needed on a subsequent MemoizingEvaluator#evaluate call,
     * it will be re-executed.
     */
    TRANSIENT,

    /**
     * An error that is completely deterministic and persistent in terms of the computation's
     * inputs. Persistent errors may be cached.
     */
    PERSISTENT;
  }

  private final Transience transience;
  @Nullable
  private final SkyKey rootCause;

  public SkyFunctionException(Exception cause, Transience transience) {
    this(cause, transience, null);
  }

  /** Used to rethrow a child error that the parent cannot handle. */
  public SkyFunctionException(Exception cause, SkyKey childKey) {
    this(cause, Transience.PERSISTENT, childKey);
  }

  private SkyFunctionException(Exception cause, Transience transience, SkyKey rootCause) {
    super(Preconditions.checkNotNull(cause));
    SkyFunctionException.validateExceptionType(cause.getClass());
    this.transience = transience;
    this.rootCause = rootCause;
  }

  @Nullable
  public final SkyKey getRootCauseSkyKey() {
    return rootCause;
  }

  public final boolean isTransient() {
    return transience == Transience.TRANSIENT;
  }

  /**
   * Catastrophic failures halt the build even when in keepGoing mode.
   */
  public boolean isCatastrophic() {
    return false;
  }

  @Override
  public synchronized Exception getCause() {
    return (Exception) super.getCause();
  }

  static <E extends Exception> void validateExceptionType(Class<E> exceptionClass) {
    if (exceptionClass.equals(ValueOrExceptionUtils.BottomException.class)) {
      return;
    }

    if (exceptionClass.isAssignableFrom(RuntimeException.class)) {
      throw new IllegalStateException(exceptionClass.getSimpleName() + " is a supertype of "
          + "RuntimeException. Don't do this since then you would potentially swallow all "
          + "RuntimeExceptions, even those from Skyframe");
    }
    if (RuntimeException.class.isAssignableFrom(exceptionClass)) {
      throw new IllegalStateException(exceptionClass.getSimpleName() + " is a subtype of "
          + "RuntimeException. You should rewrite your code to use checked exceptions.");
    }
    if (InterruptedException.class.isAssignableFrom(exceptionClass)) {
      throw new IllegalStateException(exceptionClass.getSimpleName() + " is a subtype of "
          + "InterruptedException. Don't do this; Skyframe handles interrupts separately from the "
          + "general SkyFunctionException mechanism.");
    }
  }

  /** A {@link SkyFunctionException} with a definite root cause. */
  public static class ReifiedSkyFunctionException extends SkyFunctionException {
    private final boolean isCatastrophic;

    public ReifiedSkyFunctionException(SkyFunctionException e, SkyKey key) {
      this(e.getCause(), e.transience, key, e.getRootCauseSkyKey(), e.isCatastrophic());
    }

    protected ReifiedSkyFunctionException(
        Exception cause,
        Transience transience,
        SkyKey key,
        @Nullable SkyKey rootCauseSkyKey,
        boolean isCatastrophic) {
      super(cause, transience, rootCauseSkyKey == null ? key : rootCauseSkyKey);
      this.isCatastrophic = isCatastrophic;
    }

    @Override
    public boolean isCatastrophic() {
      return isCatastrophic;
    }
  }
}