Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for in-memory outputs to output reuse #23422

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.util.Locale;
import javax.annotation.Nullable;
Expand All @@ -38,7 +37,7 @@
@SuppressWarnings("GoodTime") // Use ints instead of Durations to improve build time (cl/505728570)
public interface SpawnResult {

int POSIX_TIMEOUT_EXIT_CODE = /*SIGNAL_BASE=*/ 128 + /*SIGALRM=*/ 14;
int POSIX_TIMEOUT_EXIT_CODE = /* SIGNAL_BASE= */ 128 + /* SIGALRM= */ 14;

/** The status of the attempted Spawn execution. */
enum Status {
Expand Down Expand Up @@ -262,14 +261,11 @@ default String getFailureMessage() {
* ExecutionRequirements#REMOTE_EXECUTION_INLINE_OUTPUTS}.
*/
@Nullable
default InputStream getInMemoryOutput(ActionInput output) {
default ByteString getInMemoryOutput(ActionInput output) {
return null;
}

String getDetailMessage(
String message,
boolean catastrophe,
boolean forciblyRunRemotely);
String getDetailMessage(String message, boolean catastrophe, boolean forciblyRunRemotely);

/** Returns a file path to the action metadata log. */
@Nullable
Expand Down Expand Up @@ -434,11 +430,8 @@ public String getFailureMessage() {

@Override
public String getDetailMessage(
String message,
boolean catastrophe,
boolean forciblyRunRemotely) {
TerminationStatus status = new TerminationStatus(
exitCode(), status() == Status.TIMEOUT);
String message, boolean catastrophe, boolean forciblyRunRemotely) {
TerminationStatus status = new TerminationStatus(exitCode(), status() == Status.TIMEOUT);
String reason = "(" + status.toShortString() + ")"; // e.g. "(Exit 1)"
String explanation = Strings.isNullOrEmpty(message) ? "" : ": " + message;

Expand All @@ -457,17 +450,18 @@ public String getDetailMessage(
explanation += " (Remote action was terminated due to Out of Memory.)";
}
if (status() != Status.TIMEOUT && forciblyRunRemotely) {
explanation += " Action tagged as local was forcibly run remotely and failed - it's "
+ "possible that the action simply doesn't work remotely";
explanation +=
" Action tagged as local was forcibly run remotely and failed - it's "
+ "possible that the action simply doesn't work remotely";
}
return reason + explanation;
}

@Nullable
@Override
public InputStream getInMemoryOutput(ActionInput output) {
public ByteString getInMemoryOutput(ActionInput output) {
if (inMemoryOutputFile != null && inMemoryOutputFile.equals(output)) {
return inMemoryContents.newInput();
return inMemoryContents;
}
return null;
}
Expand Down Expand Up @@ -603,7 +597,7 @@ public String getFailureMessage() {

@Override
@Nullable
public InputStream getInMemoryOutput(ActionInput output) {
public ByteString getInMemoryOutput(ActionInput output) {
return delegate.getInMemoryOutput(output);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.StarlarkAction.Code;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.protobuf.ByteString;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
Expand Down Expand Up @@ -312,9 +313,9 @@ private InputStream getUnusedInputListInputStream(
// Note: SpawnActionContext guarantees that the first list entry exists and corresponds to the
// executed spawn.
Artifact unusedInputsListArtifact = unusedInputsList.get();
InputStream inputStream = spawnResults.get(0).getInMemoryOutput(unusedInputsListArtifact);
if (inputStream != null) {
return inputStream;
ByteString content = spawnResults.get(0).getInMemoryOutput(unusedInputsListArtifact);
if (content != null) {
return content.newInput();
}
// Fallback to reading from disk.
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Symlinks;
import com.google.devtools.build.lib.vfs.SyscallCache;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
Expand Down Expand Up @@ -402,7 +403,11 @@ private static InputStream spawnGrep(
}

SpawnResult result = results.getFirst();
return result.getInMemoryOutput(output);
ByteString includesContent = result.getInMemoryOutput(output);
if (includesContent != null) {
return includesContent.newInput();
}
return null;
}

private static void dump(ActionExecutionContext fromContext, ActionExecutionContext toContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1458,12 +1458,20 @@ public SpawnResult waitForAndReuseOutputs(RemoteAction action, LocalExecution pr
previousExecution.action.getSpawn().getOutputFiles().stream()
.collect(toImmutableMap(output -> execRoot.getRelative(output.getExecPath()), o -> o));
Map<Path, Path> realToTmpPath = new HashMap<>();
ByteString inMemoryOutputContent = null;
String inMemoryOutputPath = null;
try {
for (String output : action.getCommand().getOutputPathsList()) {
String reencodedOutput = reencodeExternalToInternal(output);
Path sourcePath =
previousExecution.action.getRemotePathResolver().outputPathToLocalPath(reencodedOutput);
ActionInput outputArtifact = previousOutputs.get(sourcePath);
Path targetPath = action.getRemotePathResolver().outputPathToLocalPath(reencodedOutput);
inMemoryOutputContent = previousSpawnResult.getInMemoryOutput(outputArtifact);
if (inMemoryOutputContent != null) {
inMemoryOutputPath = targetPath.relativeTo(execRoot).getPathString();
continue;
}
Path tmpPath = tempPathGenerator.generateTempPath();
tmpPath.getParentDirectory().createDirectoryAndParents();
try {
Expand All @@ -1476,7 +1484,6 @@ public SpawnResult waitForAndReuseOutputs(RemoteAction action, LocalExecution pr
FileSystemUtils.copyFile(sourcePath, tmpPath);
}

Path targetPath = action.getRemotePathResolver().outputPathToLocalPath(reencodedOutput);
realToTmpPath.put(targetPath, tmpPath);
} catch (FileNotFoundException e) {
// The spawn this action was deduplicated against failed to create an output file. If the
Expand Down Expand Up @@ -1517,6 +1524,21 @@ public SpawnResult waitForAndReuseOutputs(RemoteAction action, LocalExecution pr
throw e;
}

if (inMemoryOutputPath != null) {
String finalInMemoryOutputPath = inMemoryOutputPath;
ByteString finalInMemoryOutputContent = inMemoryOutputContent;
return new SpawnResult.DelegateSpawnResult(previousSpawnResult) {
@Override
@Nullable
public ByteString getInMemoryOutput(ActionInput output) {
if (output.getExecPathString().equals(finalInMemoryOutputPath)) {
return finalInMemoryOutputContent;
}
return null;
}
};
}

return previousSpawnResult;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.devtools.build.skyframe.SkyframeLookupResult;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -1508,16 +1509,11 @@ e, createFailureDetail("OutErr copy failure", Code.COPY_OUT_ERR_FAILURE)),
}

@Nullable
private byte[] getDotDContents(SpawnResult spawnResult) throws EnvironmentalExecException {
private byte[] getDotDContents(SpawnResult spawnResult) {
if (getDotdFile() != null) {
InputStream in = spawnResult.getInMemoryOutput(getDotdFile());
if (in != null) {
try {
return ByteStreams.toByteArray(in);
} catch (IOException e) {
throw new EnvironmentalExecException(
e, createFailureDetail("Reading in-memory .d file failed", Code.D_FILE_READ_FAILURE));
}
ByteString content = spawnResult.getInMemoryOutput(getDotdFile());
if (content != null) {
return content.toByteArray();
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.view.proto.Deps;
import com.google.protobuf.ByteString;
import com.google.protobuf.ExtensionRegistry;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -842,11 +843,11 @@ private static Deps.Dependencies readExecutorJdeps(
Artifact outputDepsProto,
ActionExecutionContext actionExecutionContext)
throws IOException {
InputStream inMemoryOutput = spawnResult.getInMemoryOutput(outputDepsProto);
ByteString inMemoryOutput = spawnResult.getInMemoryOutput(outputDepsProto);
try (InputStream inputStream =
inMemoryOutput == null
? actionExecutionContext.getInputPath(outputDepsProto).getInputStream()
: inMemoryOutput) {
: inMemoryOutput.newInput()) {
return Deps.Dependencies.parseFrom(inputStream, ExtensionRegistry.getEmptyRegistry());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void getTimeoutMessageNoTime() {
}

@Test
public void inMemoryContents() throws Exception {
public void inMemoryContents() {
ActionInput output = ActionInputHelper.fromPath("/foo/bar");
ByteString contents = ByteString.copyFromUtf8("hello world");

Expand All @@ -78,7 +78,7 @@ public void inMemoryContents() throws Exception {
.setInMemoryOutput(output, contents)
.build();

assertThat(ByteString.readFrom(r.getInMemoryOutput(output))).isEqualTo(contents);
assertThat(r.getInMemoryOutput(output)).isEqualTo(contents);
assertThat(r.getInMemoryOutput(null)).isEqualTo(null);
assertThat(r.getInMemoryOutput(ActionInputHelper.fromPath("/does/not/exist"))).isEqualTo(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
import com.google.devtools.build.lib.vfs.SyscallCache;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import com.google.devtools.common.options.Options;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.time.Duration;
import java.util.Set;
Expand Down Expand Up @@ -833,9 +834,9 @@ public boolean mayModifySpawnOutputsAfterExecution() {
try {
secondCacheHandleRef.set(cache.lookup(secondSpawn, secondPolicy));
} catch (InterruptedException
| IOException
| ExecException
| ForbiddenActionInputException e) {
| IOException
| ExecException
| ForbiddenActionInputException e) {
throw new IllegalStateException(e);
}
});
Expand Down Expand Up @@ -876,12 +877,59 @@ public boolean mayModifySpawnOutputsAfterExecution() {
assertThat(secondCacheHandle.hasResult()).isTrue();
assertThat(secondCacheHandle.getResult().getRunnerName()).isEqualTo("deduplicated");
assertThat(
FileSystemUtils.readContent(
fs.getPath("/exec/root/bazel-bin/k8-opt/bin/output"), UTF_8))
FileSystemUtils.readContent(
fs.getPath("/exec/root/bazel-bin/k8-opt/bin/output"), UTF_8))
.isEqualTo("hello");
assertThat(secondCacheHandle.willStore()).isFalse();
}

@Test
public void pathMappedActionWithInMemoryOutputIsDeduplicated() throws Exception {
// arrange
RemoteSpawnCache cache = createRemoteSpawnCache();

SimpleSpawn firstSpawn = simplePathMappedSpawn("k8-fastbuild");
FakeActionInputFileCache firstFakeFileCache = new FakeActionInputFileCache(execRoot);
firstFakeFileCache.createScratchInput(firstSpawn.getInputFiles().getSingleton(), "xyz");
SpawnExecutionContext firstPolicy =
createSpawnExecutionContext(firstSpawn, execRoot, firstFakeFileCache, outErr);

SimpleSpawn secondSpawn = simplePathMappedSpawn("k8-opt");
FakeActionInputFileCache secondFakeFileCache = new FakeActionInputFileCache(execRoot);
secondFakeFileCache.createScratchInput(secondSpawn.getInputFiles().getSingleton(), "xyz");
SpawnExecutionContext secondPolicy =
createSpawnExecutionContext(secondSpawn, execRoot, secondFakeFileCache, outErr);

RemoteExecutionService remoteExecutionService = cache.getRemoteExecutionService();
Mockito.doCallRealMethod().when(remoteExecutionService).waitForAndReuseOutputs(any(), any());
// Simulate a very slow upload to the remote cache to ensure that the second spawn is
// deduplicated rather than a cache hit. This is a slight hack, but also avoid introducing
// concurrency to this test.
Mockito.doNothing().when(remoteExecutionService).uploadOutputs(any(), any(), any());

// act
try (CacheHandle firstCacheHandle = cache.lookup(firstSpawn, firstPolicy)) {
firstCacheHandle.store(
new SpawnResult.Builder()
.setExitCode(0)
.setStatus(Status.SUCCESS)
.setRunnerName("test")
.setInMemoryOutput(
firstSpawn.getOutputFiles().getFirst(), ByteString.copyFromUtf8("in-memory"))
.build());
}
CacheHandle secondCacheHandle = cache.lookup(secondSpawn, secondPolicy);

// assert
ActionInput inMemoryOutput = secondSpawn.getOutputFiles().getFirst();
assertThat(secondCacheHandle.hasResult()).isTrue();
assertThat(secondCacheHandle.getResult().getRunnerName()).isEqualTo("deduplicated");
assertThat(secondCacheHandle.getResult().getInMemoryOutput(inMemoryOutput).toStringUtf8())
.isEqualTo("in-memory");
assertThat(execRoot.getRelative(inMemoryOutput.getExecPath()).exists()).isFalse();
assertThat(secondCacheHandle.willStore()).isFalse();
}

@Test
public void deduplicatedActionWithNonZeroExitCodeIsACacheMiss() throws Exception {
// arrange
Expand Down
Loading