Return REMOTE_CACHE_EVICTED exit code on ThinLTO imports cache eviction. ThinLTO backend compilation reads `.imports` files during Skyframe input discovery prior to spawn execution. When remote cache eviction drops an imports file, `LtoBackendAction.computeImports` caught the cache miss as a generic `IMPORTS_READ_IO_EXCEPTION` (exit code 1). Because Bazel only triggers `--experimental_remote_cache_eviction_retries` when an action returns `REMOTE_CACHE_EVICTED` (exit code 39), builds fail permanently instead of retrying. In `LtoBackendAction.computeImports`, check if an `IOException` reading the imports file is a `CacheNotFoundException` or `BulkTransferException` cache miss. If so, return `Spawn.Code.REMOTE_CACHE_EVICTED` so that input discovery failures from evicted cache entries trigger build retries. Tested: Added `discoverInputs_cacheNotFoundException_throwsRemoteCacheEvictedCode` and `discoverInputs_bulkTransferException_throwsRemoteCacheEvictedCode` in `LtoBackendActionTest`. PiperOrigin-RevId: 971771054 Change-Id: I6c9b84c9a55d727cd930319178a946332df2d977
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD index 5a2e91c..2e15200 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -90,6 +90,8 @@ "//src/main/java/com/google/devtools/build/lib/packages:provider", "//src/main/java/com/google/devtools/build/lib/packages/semantics", "//src/main/java/com/google/devtools/build/lib/profiler", + "//src/main/java/com/google/devtools/build/lib/remote/common:bulk_transfer_exception", + "//src/main/java/com/google/devtools/build/lib/remote/common:cache_not_found_exception", "//src/main/java/com/google/devtools/build/lib/rules:alias", "//src/main/java/com/google/devtools/build/lib/skyframe:action_execution_value", "//src/main/java/com/google/devtools/build/lib/skyframe:bzl_load_value",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java index b3291af..4b7959f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java +++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LtoBackendAction.java
@@ -38,9 +38,12 @@ import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.remote.common.BulkTransferException; +import com.google.devtools.build.lib.remote.common.CacheNotFoundException; import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; import com.google.devtools.build.lib.server.FailureDetails.LtoAction; import com.google.devtools.build.lib.server.FailureDetails.LtoAction.Code; +import com.google.devtools.build.lib.server.FailureDetails.Spawn; import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.util.DetailedExitCode; @@ -219,7 +222,18 @@ String.format( "error reading imports file %s: %s", actionExecutionContext.getInputPath(imports), e.getMessage()); - DetailedExitCode code = createDetailedExitCode(message, Code.IMPORTS_READ_IO_EXCEPTION); + DetailedExitCode code; + if (e instanceof CacheNotFoundException + || BulkTransferException.allCausedByCacheNotFoundException(e)) { + code = + DetailedExitCode.of( + FailureDetail.newBuilder() + .setMessage(message) + .setSpawn(Spawn.newBuilder().setCode(Spawn.Code.REMOTE_CACHE_EVICTED)) + .build()); + } else { + code = createDetailedExitCode(message, Code.IMPORTS_READ_IO_EXCEPTION); + } throw new ActionExecutionException(message, e, this, false, code); }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD index 092d71c..a72fa10 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD +++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/BUILD
@@ -409,20 +409,25 @@ "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster", "//src/main/java/com/google/devtools/build/lib/collect/nestedset", "//src/main/java/com/google/devtools/build/lib/events", + "//src/main/java/com/google/devtools/build/lib/remote/common:bulk_transfer_exception", + "//src/main/java/com/google/devtools/build/lib/remote/common:cache_not_found_exception", "//src/main/java/com/google/devtools/build/lib/rules/cpp", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils:depsutils", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils:dumper", + "//src/main/java/com/google/devtools/build/lib/util:exit_code", "//src/main/java/com/google/devtools/build/lib/util/io", "//src/main/java/com/google/devtools/build/lib/vfs", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", + "//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs", "//src/test/java/com/google/devtools/build/lib/actions/util", "//src/test/java/com/google/devtools/build/lib/analysis/util", "//src/test/java/com/google/devtools/build/lib/exec/util", "//third_party/java/guava:collect", "//third_party/java/junit", "//third_party/java/truth", + "@remoteapis//:build_bazel_remote_execution_v2_remote_execution_java_proto", ], )
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java index 31b61b0..c84f964 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java +++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/LtoBackendActionTest.java
@@ -18,6 +18,7 @@ import static com.google.devtools.build.lib.skyframe.serialization.testutils.Dumper.dumpStructureWithEquivalenceReduction; import static org.junit.Assert.assertThrows; +import build.bazel.remote.execution.v2.Digest; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -29,6 +30,7 @@ import com.google.devtools.build.lib.actions.ActionExecutionException; import com.google.devtools.build.lib.actions.ActionInputPrefetcher; import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.ArtifactRoot; import com.google.devtools.build.lib.actions.CommandLines; import com.google.devtools.build.lib.actions.DiscoveredModulesPruner; import com.google.devtools.build.lib.actions.Executor; @@ -44,13 +46,22 @@ import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.events.StoredEventHandler; import com.google.devtools.build.lib.exec.util.TestExecutorBuilder; +import com.google.devtools.build.lib.remote.common.BulkTransferException; +import com.google.devtools.build.lib.remote.common.CacheNotFoundException; import com.google.devtools.build.lib.skyframe.serialization.ArrayCodec; import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationDepsUtils; import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester; +import com.google.devtools.build.lib.util.ExitCode; import com.google.devtools.build.lib.util.io.FileOutErr; +import com.google.devtools.build.lib.vfs.DigestHashFunction; +import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.build.lib.vfs.SyscallCache; +import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; +import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; import org.junit.Before; @@ -280,6 +291,76 @@ } @Test + public void discoverInputs_cacheNotFoundException_throwsRemoteCacheEvictedCode() + throws Exception { + FileSystem customFs = + new InMemoryFileSystem(DigestHashFunction.SHA256) { + @Override + public synchronized InputStream getInputStream(PathFragment path) throws IOException { + if (path.getBaseName().endsWith(".imports")) { + throw new CacheNotFoundException(Digest.getDefaultInstance(), path); + } + return super.getInputStream(path); + } + }; + ArtifactRoot root = ArtifactRoot.asSourceRoot(Root.fromPath(customFs.getPath("/"))); + Artifact importsArtifact = ActionsTestUtil.createArtifact(root, "bitcode1.imports"); + Artifact index1Artifact = getSourceArtifact("file2.o"); + LtoBackendAction action = + LtoBackendAction.create( + ActionsTestUtil.NULL_ACTION_OWNER, + targetConfig, + NestedSetBuilder.create(Order.STABLE_ORDER, importsArtifact, index1Artifact), + new BitcodeFiles(NestedSetBuilder.create(Order.STABLE_ORDER, index1Artifact)), + importsArtifact, + ImmutableSet.of(destinationArtifact), + CommandLines.builder() + .addSingleArgument(scratch.file("/bin/clang").asFragment()) + .build(), + ActionEnvironment.create(ImmutableMap.of())); + + ActionExecutionException e = + assertThrows(ActionExecutionException.class, () -> action.discoverInputs(context)); + + assertThat(e.getExitCode()).isEqualTo(ExitCode.REMOTE_CACHE_EVICTED); + } + + @Test + public void discoverInputs_bulkTransferException_throwsRemoteCacheEvictedCode() throws Exception { + FileSystem customFs = + new InMemoryFileSystem(DigestHashFunction.SHA256) { + @Override + public synchronized InputStream getInputStream(PathFragment path) throws IOException { + if (path.getBaseName().endsWith(".imports")) { + throw new BulkTransferException( + new CacheNotFoundException(Digest.getDefaultInstance(), path)); + } + return super.getInputStream(path); + } + }; + ArtifactRoot root = ArtifactRoot.asSourceRoot(Root.fromPath(customFs.getPath("/"))); + Artifact importsArtifact = ActionsTestUtil.createArtifact(root, "bitcode1.imports"); + Artifact index1Artifact = getSourceArtifact("file2.o"); + LtoBackendAction action = + LtoBackendAction.create( + ActionsTestUtil.NULL_ACTION_OWNER, + targetConfig, + NestedSetBuilder.create(Order.STABLE_ORDER, importsArtifact, index1Artifact), + new BitcodeFiles(NestedSetBuilder.create(Order.STABLE_ORDER, index1Artifact)), + importsArtifact, + ImmutableSet.of(destinationArtifact), + CommandLines.builder() + .addSingleArgument(scratch.file("/bin/clang").asFragment()) + .build(), + ActionEnvironment.create(ImmutableMap.of())); + + ActionExecutionException e = + assertThrows(ActionExecutionException.class, () -> action.discoverInputs(context)); + + assertThat(e.getExitCode()).isEqualTo(ExitCode.REMOTE_CACHE_EVICTED); + } + + @Test public void serializationRoundTrip_resetsInputs() throws Exception { LtoBackendAction action = LtoBackendAction.create(