When loading a package, if an interrupted exception is thrown but an error was already encountered, suppress the interrupted exception in favor of the package-specific error: the error may be the one needed for error bubbling, while the interruption could just mean that we were interrupted during error bubbling.
In the non-error-bubbling case, it's ~neutral to propagate the SkyFunction-specific exception instead of the InterruptedException: likely neither will be listened to, since the interrupt of a Skyframe thread indicates the threadpool is already shutting down.
PiperOrigin-RevId: 361889324
diff --git a/src/main/java/com/google/devtools/build/lib/packages/BUILD b/src/main/java/com/google/devtools/build/lib/packages/BUILD
index 10e79dd..61bfbcd 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/packages/BUILD
@@ -58,6 +58,7 @@
"//src/main/protobuf:build_java_proto",
"//src/main/protobuf:failure_details_java_proto",
"//third_party:auto_value",
+ "//third_party:flogger",
"//third_party:guava",
"//third_party:jsr305",
"//third_party/protobuf:protobuf_java",
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index efcd7bf..a118989 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -20,6 +20,7 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryName;
@@ -89,6 +90,7 @@
* per client application.
*/
public final class PackageFactory {
+ private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
/** An extension to the global namespace of the BUILD language. */
// TODO(bazel-team): this should probably be renamed PackageFactory.RuntimeExtension
@@ -647,6 +649,16 @@
pkgContext.eventHandler.handle(
Package.error(null, ex.getMessageWithStack(), Code.STARLARK_EVAL_ERROR));
pkgBuilder.setContainsErrors();
+ } catch (InterruptedException ex) {
+ if (pkgContext.pkgBuilder.containsErrors()) {
+ // Suppress the interrupted exception: we have an error of our own to return.
+ Thread.currentThread().interrupt();
+ logger.atInfo().withCause(ex).log(
+ "Suppressing InterruptedException for Package %s because an error was also found",
+ pkgBuilder.getPackageIdentifier().getCanonicalForm());
+ } else {
+ throw ex;
+ }
}
pkgBuilder.setComputationSteps(thread.getExecutedSteps());
}
diff --git a/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java b/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java
index 0ba2590..bdbde01 100644
--- a/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java
+++ b/src/main/java/com/google/devtools/build/skyframe/SkyFunction.java
@@ -260,7 +260,10 @@
* <p>Callers should prioritize their responsibility to detect and handle errors in the returned
* map over their responsibility to return {@code null} if values are missing. This is because
* in nokeep_going evaluations, an error from a low level dependency is given a chance to be
- * enriched by its reverse-dependencies, if possible.
+ * enriched by its reverse-dependencies, if possible. Callers should also prioritize throwing
+ * exceptions over checking for {@link InterruptedException}, since during the error-bubbling
+ * enrichment process, the SkyFunction is interrupted after it has received the exception to
+ * prevent it from doing too much unnecessary work.
*
* <p>Returns a map, {@code m}. For all {@code k} in {@code depKeys}, {@code m.get(k) != null}.
* For all {@code v} such that there is some {@code k} such that {@code m.get(k) == v}, the
@@ -368,7 +371,8 @@
* ks.contains(k)
* </ul>
*
- * <p>If this returns true, the {@link SkyFunction} must return {@code null}.
+ * <p>If this returns true, the {@link SkyFunction} must return {@code null} or throw a {@link
+ * SkyFunctionException} if it detected an error even with values missing.
*/
boolean valuesMissing();
@@ -381,7 +385,8 @@
/**
* A live view of deps known to have already been requested either through an earlier call to
* {@link SkyFunction#compute} or inferred during change pruning. Should return {@code null} if
- * unknown.
+ * unknown. Only for special use cases: do not use in general unless you know exactly what
+ * you're doing!
*/
@Nullable
default GroupedList<SkyKey> getTemporaryDirectDeps() {
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/AnalysisWithIOExceptionsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/AnalysisWithIOExceptionsTest.java
index 03115e3..87ce6b1 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/AnalysisWithIOExceptionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/AnalysisWithIOExceptionsTest.java
@@ -20,8 +20,10 @@
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileStatus;
import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.ModifiedFileSet;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Root;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import java.io.IOException;
import java.util.function.Function;
@@ -58,6 +60,24 @@
}
@Test
+ public void testIncrementalGlobIOException() throws Exception {
+ scratch.file("b/BUILD", "sh_library(name = 'b', deps= ['//a:a'])");
+ scratch.file(
+ "a/BUILD",
+ "sh_library(name = 'a', srcs = glob(['a.sh']))",
+ "sh_library(name = 'expensive', srcs = ['expensive.sh'])");
+ Path aShFile = scratch.file("a/a.sh");
+ update("//b:b");
+ skyframeExecutor.invalidateFilesUnderPathForTesting(
+ reporter,
+ ModifiedFileSet.builder().modify(aShFile.relativeTo(rootDirectory)).build(),
+ Root.fromPath(rootDirectory));
+ crashMessage = path -> path.toString().contains("a.sh") ? "bork" : null;
+ reporter.removeHandler(failFastHandler);
+ assertThrows(ViewCreationFailedException.class, () -> update("//b:b"));
+ }
+
+ @Test
public void testWorkspaceError() throws IOException {
scratch.file("a/BUILD");
crashMessage = path -> path.toString().contains("WORKSPACE") ? "bork" : null;