Inject remote metadata for action stdout/stderr. This will be necessary once RemoteActionFileSystem becomes a MetadataProvider in a followup change. Otherwise, a spawn receiving the stdout/stderr of a previous spawn inside the same action (as is the case with test rules) will fail to find the metadata and crash. PiperOrigin-RevId: 515593787 Change-Id: I97e129e7f7537560a2fb6f8cbce8773e0d56ad0d
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java index b5f598a..49d11f1 100644 --- a/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java +++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java
@@ -108,6 +108,7 @@ import com.google.devtools.build.lib.server.FailureDetails.RemoteExecution; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.util.io.FileOutErr; +import com.google.devtools.build.lib.vfs.DigestUtils; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; @@ -808,7 +809,11 @@ } private void injectRemoteArtifacts( - RemoteAction action, ActionResultMetadata metadata, long expireAtEpochMilli) + RemoteAction action, + ActionResult actionResult, + ActionResultMetadata metadata, + FileOutErr outErr, + long expireAtEpochMilli) throws IOException { FileSystem actionFileSystem = action.getSpawnExecutionContext().getActionFileSystem(); checkState(actionFileSystem instanceof RemoteActionFileSystem); @@ -839,6 +844,47 @@ file.digest().getSizeBytes(), expireAtEpochMilli); } + + // Inject the metadata for the stdout/stderr, which could be inputs to a followup spawn in the + // same action (as is the case e.g. with test rules). + + maybeInjectRemoteArtifactForStdoutOrStderr( + remoteActionFileSystem, + outErr.getOutputPath(), + actionResult.hasStdoutDigest() ? actionResult.getStdoutDigest() : null, + actionResult.getStdoutRaw().size(), + expireAtEpochMilli); + + maybeInjectRemoteArtifactForStdoutOrStderr( + remoteActionFileSystem, + outErr.getErrorPath(), + actionResult.hasStderrDigest() ? actionResult.getStderrDigest() : null, + actionResult.getStderrRaw().size(), + expireAtEpochMilli); + } + + private void maybeInjectRemoteArtifactForStdoutOrStderr( + RemoteActionFileSystem remoteActionFileSystem, + Path path, + @Nullable Digest digest, + long inlinedSize, + long expireAtEpochMilli) + throws IOException { + if (digest != null) { + remoteActionFileSystem.injectRemoteFile( + path.asFragment(), + DigestUtil.toBinaryDigest(digest), + digest.getSizeBytes(), + expireAtEpochMilli); + } else if (inlinedSize > 0) { + // If we didn't get a digest back from the service, but we did get the inlined contents, + // compute the digest from the output file previously written by downloadOutputs. + remoteActionFileSystem.injectRemoteFile( + path.asFragment(), + DigestUtils.manuallyComputeDigest(path, inlinedSize), + inlinedSize, + expireAtEpochMilli); + } } /** In-memory representation of action result metadata. */ @@ -1167,7 +1213,7 @@ } var expireAtEpochMilli = Instant.now().plus(remoteOptions.remoteCacheTtl).toEpochMilli(); - injectRemoteArtifacts(action, metadata, expireAtEpochMilli); + injectRemoteArtifacts(action, result.actionResult, metadata, outErr, expireAtEpochMilli); try (SilentCloseable c = Profiler.instance().profile("Remote.downloadInMemoryOutput")) { if (inMemoryOutput != null) {
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteExecutionServiceTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteExecutionServiceTest.java index 2eeee1a..ec6e25f 100644 --- a/src/test/java/com/google/devtools/build/lib/remote/RemoteExecutionServiceTest.java +++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteExecutionServiceTest.java
@@ -1133,9 +1133,7 @@ } @Test - public void downloadOutputs_stdoutAndStdErrWithMinimal_works() throws Exception { - // Test that downloading of non-embedded stdout and stderr works - + public void downloadOutputs_nonInlinedStdoutAndStderrWithMinimal_works() throws Exception { // arrange Digest dOut = cache.addContents(remoteActionExecutionContext, "stdout"); Digest dErr = cache.addContents(remoteActionExecutionContext, "stderr"); @@ -1159,6 +1157,51 @@ InMemoryOutput inMemoryOutput = service.downloadOutputs(action, result); // assert + verify(actionFileSystem) + .injectRemoteFile( + eq(outErr.getOutputPathFragment()), eq(toBinaryDigest(dOut)), eq(6L), anyLong()); + verify(actionFileSystem) + .injectRemoteFile( + eq(outErr.getErrorPathFragment()), eq(toBinaryDigest(dErr)), eq(6L), anyLong()); + assertThat(inMemoryOutput).isNull(); + assertThat(outErr.outAsLatin1()).isEqualTo("stdout"); + assertThat(outErr.errAsLatin1()).isEqualTo("stderr"); + Path outputBase = checkNotNull(artifactRoot.getRoot().asPath()); + assertThat(outputBase.readdir(Symlinks.NOFOLLOW)).isEmpty(); + assertThat(context.isLockOutputFilesCalled()).isTrue(); + } + + @Test + public void downloadOutputs_inlinedStdoutAndStderrWithMinimal_works() throws Exception { + // arrange + Digest dOut = digestUtil.compute("stdout".getBytes(UTF_8)); + Digest dErr = digestUtil.compute("stderr".getBytes(UTF_8)); + ActionResult r = + ActionResult.newBuilder() + .setExitCode(0) + .setStdoutRaw(ByteString.copyFromUtf8("stdout")) + .setStderrRaw(ByteString.copyFromUtf8("stderr")) + .build(); + + RemoteActionResult result = RemoteActionResult.createFromCache(CachedActionResult.remote(r)); + Spawn spawn = newSpawnFromResult(result); + RemoteActionFileSystem actionFileSystem = mock(RemoteActionFileSystem.class); + FakeSpawnExecutionContext context = newSpawnExecutionContext(spawn, actionFileSystem); + RemoteOptions remoteOptions = Options.getDefaults(RemoteOptions.class); + remoteOptions.remoteOutputsMode = RemoteOutputsMode.MINIMAL; + RemoteExecutionService service = newRemoteExecutionService(remoteOptions); + RemoteAction action = service.buildRemoteAction(spawn, context); + + // act + InMemoryOutput inMemoryOutput = service.downloadOutputs(action, result); + + // assert + verify(actionFileSystem) + .injectRemoteFile( + eq(outErr.getOutputPathFragment()), eq(toBinaryDigest(dOut)), eq(6L), anyLong()); + verify(actionFileSystem) + .injectRemoteFile( + eq(outErr.getErrorPathFragment()), eq(toBinaryDigest(dErr)), eq(6L), anyLong()); assertThat(inMemoryOutput).isNull(); assertThat(outErr.outAsLatin1()).isEqualTo("stdout"); assertThat(outErr.errAsLatin1()).isEqualTo("stderr");