Always report when a package is done to the PackageProgressReceiver.
Previously, if a package was so broken that a even an errored PackageValue couldn't be created for it, it would hang around in the UI ("currently loading //some/broken/package") for the entire analysis-loading phase. This behavior was because PackageProgressReceiver didn't receive a done notification from PackageFunction in extreme error cases. The fix is to call PackageProgressReceiver.doneReadPackage even when exceptions happen during loading.
While we're here, save a level of indentation in PackageFunction.loadPackage() by moving the cache checking logic to the single caller.
Closes #8209.
PiperOrigin-RevId: 259439995
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index 23a6c77..4ead9ee 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -438,20 +438,24 @@
List<Statement> preludeStatements =
astLookupValue.lookupSuccessful()
? astLookupValue.getAST().getStatements() : ImmutableList.<Statement>of();
- LoadedPackageCacheEntry packageCacheEntry =
- loadPackage(
- workspaceName,
- repositoryMapping,
- packageId,
- buildFileRootedPath,
- buildFileValue,
- defaultVisibility,
- starlarkSemantics,
- preludeStatements,
- packageLookupValue.getRoot(),
- env);
+ LoadedPackageCacheEntry packageCacheEntry = packageFunctionCache.getIfPresent(packageId);
if (packageCacheEntry == null) {
- return null;
+ packageCacheEntry =
+ loadPackage(
+ workspaceName,
+ repositoryMapping,
+ packageId,
+ buildFileRootedPath,
+ buildFileValue,
+ defaultVisibility,
+ starlarkSemantics,
+ preludeStatements,
+ packageLookupValue.getRoot(),
+ env);
+ if (packageCacheEntry == null) {
+ return null;
+ }
+ packageFunctionCache.put(packageId, packageCacheEntry);
}
Package.Builder pkgBuilder = packageCacheEntry.builder;
try {
@@ -1095,99 +1099,99 @@
Root packageRoot,
Environment env)
throws InterruptedException, PackageFunctionException {
- LoadedPackageCacheEntry packageCacheEntry = packageFunctionCache.getIfPresent(packageId);
- if (packageCacheEntry == null) {
- if (packageProgress != null) {
- packageProgress.startReadPackage(packageId);
- }
- try (SilentCloseable c =
- Profiler.instance().profile(ProfilerTask.CREATE_PACKAGE, packageId.toString())) {
- AstParseResult astParseResult = astCache.getIfPresent(packageId);
- Path inputFile = buildFilePath.asPath();
- if (astParseResult == null) {
- if (showLoadingProgress.get()) {
- env.getListener().handle(Event.progress("Loading package: " + packageId));
- }
- ParserInputSource input;
- Preconditions.checkNotNull(buildFileValue, packageId);
- byte[] buildFileBytes = null;
- try {
- buildFileBytes =
- buildFileValue.isSpecialFile()
- ? FileSystemUtils.readContent(inputFile)
- : FileSystemUtils.readWithKnownFileSize(inputFile, buildFileValue.getSize());
- } catch (IOException e) {
- buildFileBytes =
- actionOnIOExceptionReadingBuildFile.maybeGetBuildFileContentsToUse(
- inputFile.asFragment(), e);
- if (buildFileBytes == null) {
- // Note that we did the work that led to this IOException, so we should
- // conservatively report this error as transient.
- throw new PackageFunctionException(
- new BuildFileContainsErrorsException(packageId, e.getMessage(), e),
- Transience.TRANSIENT);
- }
- // If control flow reaches here, we're in territory that is deliberately unsound.
- // See the javadoc for ActionOnIOExceptionReadingBuildFile.
- }
- input =
- ParserInputSource.create(
- FileSystemUtils.convertFromLatin1(buildFileBytes), inputFile.asFragment());
- StoredEventHandler astParsingEventHandler = new StoredEventHandler();
- BuildFileAST ast =
- PackageFactory.parseBuildFile(
- packageId, input, preludeStatements, repositoryMapping, astParsingEventHandler);
- astParseResult = new AstParseResult(ast, astParsingEventHandler);
- astCache.put(packageId, astParseResult);
- }
- SkylarkImportResult importResult;
- try {
- importResult =
- fetchImportsFromBuildFile(
- buildFilePath,
- packageId,
- astParseResult.ast,
- /* workspaceChunk = */ -1,
- env,
- skylarkImportLookupFunctionForInlining);
- } catch (NoSuchPackageException e) {
- throw new PackageFunctionException(e, Transience.PERSISTENT);
- } catch (InterruptedException e) {
- astCache.invalidate(packageId);
- throw e;
- }
- if (importResult == null) {
- return null;
- }
- astCache.invalidate(packageId);
- GlobberWithSkyframeGlobDeps globberWithSkyframeGlobDeps =
- makeGlobber(inputFile, packageId, packageRoot, env);
- long startTimeNanos = BlazeClock.nanoTime();
- Package.Builder pkgBuilder =
- packageFactory.createPackageFromAst(
- workspaceName,
- repositoryMapping,
- packageId,
- buildFilePath,
- astParseResult,
- importResult.importMap,
- importResult.fileDependencies,
- defaultVisibility,
- starlarkSemantics,
- globberWithSkyframeGlobDeps);
- long loadTimeNanos = Math.max(BlazeClock.nanoTime() - startTimeNanos, 0L);
- packageCacheEntry = new LoadedPackageCacheEntry(
- pkgBuilder,
- globberWithSkyframeGlobDeps.getGlobDepsRequested(),
- loadTimeNanos);
- numPackagesLoaded.incrementAndGet();
- if (packageProgress != null) {
- packageProgress.doneReadPackage(packageId);
- }
- packageFunctionCache.put(packageId, packageCacheEntry);
- }
+ if (packageProgress != null) {
+ packageProgress.startReadPackage(packageId);
}
- return packageCacheEntry;
+ try (SilentCloseable c =
+ Profiler.instance().profile(ProfilerTask.CREATE_PACKAGE, packageId.toString())) {
+ AstParseResult astParseResult = astCache.getIfPresent(packageId);
+ Path inputFile = buildFilePath.asPath();
+ if (astParseResult == null) {
+ if (showLoadingProgress.get()) {
+ env.getListener().handle(Event.progress("Loading package: " + packageId));
+ }
+ ParserInputSource input;
+ Preconditions.checkNotNull(buildFileValue, packageId);
+ byte[] buildFileBytes = null;
+ try {
+ buildFileBytes =
+ buildFileValue.isSpecialFile()
+ ? FileSystemUtils.readContent(inputFile)
+ : FileSystemUtils.readWithKnownFileSize(inputFile, buildFileValue.getSize());
+ } catch (IOException e) {
+ buildFileBytes =
+ actionOnIOExceptionReadingBuildFile.maybeGetBuildFileContentsToUse(
+ inputFile.asFragment(), e);
+ if (buildFileBytes == null) {
+ // Note that we did the work that led to this IOException, so we should
+ // conservatively report this error as transient.
+ throw new PackageFunctionException(
+ new BuildFileContainsErrorsException(packageId, e.getMessage(), e),
+ Transience.TRANSIENT);
+ }
+ // If control flow reaches here, we're in territory that is deliberately unsound.
+ // See the javadoc for ActionOnIOExceptionReadingBuildFile.
+ }
+ input =
+ ParserInputSource.create(
+ FileSystemUtils.convertFromLatin1(buildFileBytes), inputFile.asFragment());
+ StoredEventHandler astParsingEventHandler = new StoredEventHandler();
+ BuildFileAST ast =
+ PackageFactory.parseBuildFile(
+ packageId, input, preludeStatements, repositoryMapping, astParsingEventHandler);
+ astParseResult = new AstParseResult(ast, astParsingEventHandler);
+ astCache.put(packageId, astParseResult);
+ }
+ SkylarkImportResult importResult;
+ try {
+ importResult =
+ fetchImportsFromBuildFile(
+ buildFilePath,
+ packageId,
+ astParseResult.ast,
+ /* workspaceChunk = */ -1,
+ env,
+ skylarkImportLookupFunctionForInlining);
+ } catch (NoSuchPackageException e) {
+ throw new PackageFunctionException(e, Transience.PERSISTENT);
+ } catch (InterruptedException e) {
+ astCache.invalidate(packageId);
+ throw e;
+ }
+ if (importResult == null) {
+ return null;
+ }
+ astCache.invalidate(packageId);
+ GlobberWithSkyframeGlobDeps globberWithSkyframeGlobDeps =
+ makeGlobber(inputFile, packageId, packageRoot, env);
+ long startTimeNanos = BlazeClock.nanoTime();
+ Package.Builder pkgBuilder =
+ packageFactory.createPackageFromAst(
+ workspaceName,
+ repositoryMapping,
+ packageId,
+ buildFilePath,
+ astParseResult,
+ importResult.importMap,
+ importResult.fileDependencies,
+ defaultVisibility,
+ starlarkSemantics,
+ globberWithSkyframeGlobDeps);
+ long loadTimeNanos = Math.max(BlazeClock.nanoTime() - startTimeNanos, 0L);
+ LoadedPackageCacheEntry packageCacheEntry =
+ new LoadedPackageCacheEntry(
+ pkgBuilder, globberWithSkyframeGlobDeps.getGlobDepsRequested(), loadTimeNanos);
+ numPackagesLoaded.incrementAndGet();
+ if (packageProgress != null) {
+ packageProgress.doneReadPackage(packageId);
+ }
+ return packageCacheEntry;
+ } catch (InterruptedException | PackageFunctionException e) {
+ if (packageProgress != null) {
+ packageProgress.doneReadPackage(packageId);
+ }
+ throw e;
+ }
}
private static class InternalInconsistentFilesystemException extends Exception {
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
index a8c1871..c7040c7 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
@@ -42,6 +42,7 @@
import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
import com.google.devtools.build.lib.vfs.Dirent;
import com.google.devtools.build.lib.vfs.FileStatus;
@@ -1073,6 +1074,19 @@
assertThat(pkg.getEvents()).isEmpty();
}
+ @Test
+ public void veryBrokenPackagePostsDoneToProgressReceiver() throws Exception {
+ reporter.removeHandler(failFastHandler);
+
+ scratch.file("pkg/BUILD", "load('//does_not:exist.bzl', 'broken'");
+ SkyKey key = PackageValue.key(PackageIdentifier.parse("@//pkg"));
+ EvaluationResult<PackageValue> result =
+ SkyframeExecutorTestUtils.evaluate(getSkyframeExecutor(), key, false, reporter);
+ assertThatEvaluationResult(result).hasError();
+ assertThat(getSkyframeExecutor().getPackageProgressReceiver().progressState())
+ .isEqualTo(new Pair<String, String>("1 packages loaded", ""));
+ }
+
private static class CustomInMemoryFs extends InMemoryFileSystem {
private abstract static class FileStatusOrException {
abstract FileStatus get() throws IOException;