Skip to content

Commit fa42e07

Browse files
fmeumtjgq
authored andcommitted
[8.5.0] Treat action results with missing mandatory outputs as cache misses (bazelbuild#27380)
This allows builds to recover from a polluted remote cache that contains an action result with an exit code of 0 that hasn't produced the required output files. The check added in unknown commit is moved closer to the lookup so that the cached result can be ignored instead of resulting in an error, with forced re-execution in the case of remote execution. Closes bazelbuild#27105. PiperOrigin-RevId: 821651398 Change-Id: I067c668598f9ee83e670b4636854185282112f86 (cherry picked from commit 29d442b) Closes bazelbuild#27363
1 parent b4bb270 commit fa42e07

18 files changed

Lines changed: 480 additions & 141 deletions

src/main/java/com/google/devtools/build/lib/remote/RemoteActionFileSystem.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import com.google.devtools.build.lib.vfs.FileSystem;
5858
import com.google.devtools.build.lib.vfs.Path;
5959
import com.google.devtools.build.lib.vfs.PathFragment;
60+
import com.google.devtools.build.lib.vfs.SymlinkTargetType;
6061
import com.google.devtools.build.lib.vfs.Symlinks;
6162
import com.google.devtools.build.lib.vfs.inmemoryfs.FileInfo;
6263
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryContentInfo;
@@ -579,15 +580,16 @@ protected PathFragment readSymbolicLink(PathFragment path) throws IOException {
579580
}
580581

581582
@Override
582-
protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
583+
protected void createSymbolicLink(
584+
PathFragment linkPath, PathFragment targetFragment, SymlinkTargetType type)
583585
throws IOException {
584586
linkPath = resolveSymbolicLinksForParent(linkPath);
585587

586588
if (isOutput(linkPath)) {
587-
remoteOutputTree.getPath(linkPath).createSymbolicLink(targetFragment);
589+
remoteOutputTree.getPath(linkPath).createSymbolicLink(targetFragment, type);
588590
}
589591

590-
localFs.getPath(linkPath).createSymbolicLink(targetFragment);
592+
localFs.getPath(linkPath).createSymbolicLink(targetFragment, type);
591593
}
592594

593595
@Override

src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
import com.google.devtools.build.lib.server.FailureDetails.RemoteExecution;
118118
import com.google.devtools.build.lib.util.Fingerprint;
119119
import com.google.devtools.build.lib.util.OS;
120+
import com.google.devtools.build.lib.util.StringEncoding;
120121
import com.google.devtools.build.lib.util.TempPathGenerator;
121122
import com.google.devtools.build.lib.util.io.FileOutErr;
122123
import com.google.devtools.build.lib.vfs.FileSystem;
@@ -149,6 +150,7 @@
149150
import java.util.Map;
150151
import java.util.Map.Entry;
151152
import java.util.Objects;
153+
import java.util.Optional;
152154
import java.util.Set;
153155
import java.util.SortedMap;
154156
import java.util.TreeMap;
@@ -807,6 +809,32 @@ public boolean success() {
807809
return actionResult.getExitCode() == 0;
808810
}
809811

812+
/**
813+
* Returns an output that is mandatory for the given spawn but missing from this result, if any.
814+
*/
815+
public Optional<? extends ActionInput> maybeGetMissingMandatoryOutput(RemoteAction action) {
816+
var outputFiles = Iterables.transform(getOutputFiles(), OutputFile::getPath);
817+
var outputDirPaths = Iterables.transform(getOutputDirectories(), OutputDirectory::getPath);
818+
var outputSymlinkPaths =
819+
Iterables.transform(
820+
Iterables.concat(
821+
getOutputSymlinks(), getOutputFileSymlinks(), getOutputDirectorySymlinks()),
822+
OutputSymlink::getPath);
823+
ImmutableSet<String> allOutputPaths =
824+
ImmutableSet.copyOf(
825+
Iterables.transform(
826+
Iterables.concat(outputFiles, outputDirPaths, outputSymlinkPaths),
827+
StringEncoding::unicodeToInternal));
828+
// Check that all mandatory outputs are created.
829+
var spawn = action.getSpawn();
830+
var remotePathResolver = action.getRemotePathResolver();
831+
return spawn.getOutputFiles().stream()
832+
.filter(spawn::isMandatoryOutput)
833+
.filter(
834+
output -> !allOutputPaths.contains(remotePathResolver.localPathToOutputPath(output)))
835+
.findFirst();
836+
}
837+
810838
/** Returns {@code true} if this result is from a cache. */
811839
public boolean cacheHit() {
812840
if (executeResponse == null) {
@@ -1487,23 +1515,11 @@ public InMemoryOutput downloadOutputs(RemoteAction action, RemoteActionResult re
14871515

14881516
if (result.success()) {
14891517
// Check that all mandatory outputs are created.
1490-
for (ActionInput output : action.getSpawn().getOutputFiles()) {
1491-
if (action.getSpawn().isMandatoryOutput(output)) {
1492-
// In the past, remote execution did not create output directories if the action didn't do
1493-
// this explicitly. This check only remains so that old remote cache entries that do not
1494-
// include empty output directories remain valid.
1495-
if (output instanceof Artifact && ((Artifact) output).isTreeArtifact()) {
1496-
continue;
1497-
}
1498-
1499-
Path localPath = execRoot.getRelative(output.getExecPath());
1500-
if (!metadata.files.containsKey(localPath)
1501-
&& !metadata.directories.containsKey(localPath)
1502-
&& !metadata.symlinks.containsKey(localPath)) {
1503-
throw new IOException(
1504-
String.format("mandatory output %s was not created", prettyPrint(output)));
1505-
}
1506-
}
1518+
var missingMandatoryOutput = result.maybeGetMissingMandatoryOutput(action);
1519+
if (missingMandatoryOutput.isPresent()) {
1520+
throw new IOException(
1521+
"mandatory output %s was not created"
1522+
.formatted(prettyPrint(missingMandatoryOutput.get())));
15071523
}
15081524

15091525
if (result.executeResponse != null && !knownMissingCasDigests.isEmpty()) {

src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,11 @@ public CacheHandle lookup(Spawn spawn, SpawnExecutionContext context)
147147
prof.profile(ProfilerTask.REMOTE_CACHE_CHECK, "check cache hit")) {
148148
result = remoteExecutionService.lookupCache(action);
149149
}
150-
// In case the remote cache returned a failed action (exit code != 0) we treat it as a
151-
// cache miss
152-
if (result != null && result.getExitCode() == 0) {
150+
// In case the remote cache returned a failed action (exit code != 0) or failed to create
151+
// a mandatory output, we treat it as a cache miss.
152+
if (result != null
153+
&& result.getExitCode() == 0
154+
&& result.maybeGetMissingMandatoryOutput(action).isEmpty()) {
153155
if (thisExecution != null) {
154156
thisExecution.close();
155157
}

src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,13 @@
7878
import com.google.devtools.build.lib.util.io.FileOutErr;
7979
import com.google.devtools.build.lib.vfs.Path;
8080
import com.google.longrunning.Operation;
81+
import com.google.protobuf.Any;
8182
import com.google.protobuf.Timestamp;
8283
import com.google.protobuf.util.Durations;
8384
import com.google.protobuf.util.Timestamps;
85+
import com.google.rpc.RetryInfo;
8486
import io.grpc.Status.Code;
87+
import io.grpc.protobuf.StatusProto;
8588
import java.io.IOException;
8689
import java.time.Duration;
8790
import java.util.concurrent.atomic.AtomicBoolean;
@@ -217,7 +220,8 @@ public SpawnResult exec(Spawn spawn, SpawnExecutionContext context)
217220
}
218221

219222
if (cachedResult != null) {
220-
if (cachedResult.getExitCode() != 0) {
223+
if (cachedResult.getExitCode() != 0
224+
|| cachedResult.maybeGetMissingMandatoryOutput(action).isPresent()) {
221225
// Failed actions are treated as a cache miss mostly in order to avoid caching flaky
222226
// actions (tests).
223227
// Set acceptCachedResult to false in order to force the action re-execution
@@ -312,6 +316,19 @@ public SpawnResult exec(Spawn spawn, SpawnExecutionContext context)
312316
// It's already late at this stage, but we should at least report once.
313317
reporter.reportExecutingIfNot();
314318

319+
if (result.cacheHit()
320+
&& (!result.success()
321+
|| result.maybeGetMissingMandatoryOutput(action).isPresent())) {
322+
// Instead of failing in downloadAndFinalizeSpawnResult, retry with forced execution.
323+
useCachedResult.set(false);
324+
var status =
325+
com.google.rpc.Status.newBuilder()
326+
.setCode(com.google.rpc.Code.NOT_FOUND_VALUE)
327+
.addDetails(Any.pack(RetryInfo.getDefaultInstance()))
328+
.build();
329+
throw StatusProto.toStatusRuntimeException(status);
330+
}
331+
315332
maybePrintExecutionMessages(context, result.getMessage(), result.success());
316333

317334
profileAccounting(clampTimeNanos, result.getExecutionMetadata());

src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.devtools.build.lib.vfs.FileStatus;
3030
import com.google.devtools.build.lib.vfs.Path;
3131
import com.google.devtools.build.lib.vfs.PathFragment;
32+
import com.google.devtools.build.lib.vfs.SymlinkTargetType;
3233
import java.io.File;
3334
import java.io.FileInputStream;
3435
import java.io.FileNotFoundException;
@@ -390,7 +391,8 @@ public void createDirectoryAndParents(PathFragment path) throws IOException {
390391
}
391392

392393
@Override
393-
protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
394+
protected void createSymbolicLink(
395+
PathFragment linkPath, PathFragment targetFragment, SymlinkTargetType type)
394396
throws IOException {
395397
var comp = Blocker.begin();
396398
try {

src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,14 +569,27 @@ protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) thro
569569
protected abstract boolean isSpecialFile(PathFragment path, boolean followSymlinks);
570570

571571
/**
572-
* Creates a symbolic link. See {@link Path#createSymbolicLink(Path)} for specification.
572+
* Creates a symbolic link. See {@link Path#createSymbolicLink(Path, SymlinkTargetType)} for
573+
* specification.
573574
*
574575
* <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(PathFragment)}
575576
* returns false, this method will throw an {@link UnsupportedOperationException}
576577
*/
577-
protected abstract void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
578+
protected abstract void createSymbolicLink(
579+
PathFragment linkPath, PathFragment targetFragment, SymlinkTargetType hint)
578580
throws IOException;
579581

582+
/**
583+
* Creates a symbolic link. See {@link Path#createSymbolicLink(Path)} for specification.
584+
*
585+
* <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(PathFragment)}
586+
* returns false, this method will throw an {@link UnsupportedOperationException}
587+
*/
588+
protected final void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
589+
throws IOException {
590+
createSymbolicLink(linkPath, targetFragment, SymlinkTargetType.UNSPECIFIED);
591+
}
592+
580593
/**
581594
* Returns the target of a symbolic link. See {@link Path#readSymbolicLink} for specification.
582595
*

src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,12 @@ private boolean linkExists(File file) {
279279
}
280280

281281
@Override
282-
protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
282+
protected void createSymbolicLink(
283+
PathFragment linkPath, PathFragment targetFragment, SymlinkTargetType type)
283284
throws IOException {
284285
java.nio.file.Path nioPath = getNioPath(linkPath);
285286
try {
287+
// Files.createSymbolicLink does not let us specify the target type.
286288
Files.createSymbolicLink(
287289
nioPath,
288290
Paths.get(StringEncoding.internalToPlatform(targetFragment.getSafePathString())));

src/main/java/com/google/devtools/build/lib/vfs/Path.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,11 +486,43 @@ public Path createTempDirectory(String prefix) throws IOException {
486486
* referent of the created symlink is is the absolute path "target"; it is not possible to create
487487
* relative symbolic links via this method.
488488
*
489+
* <p>The {@code type} argument denotes the file type of the target, if known. Some filesystems
490+
* require this information to correctly create a symlink. This argument may be ignored if the
491+
* target can be observed to exist and is of a different type.
492+
*
493+
* @param type the target file type
489494
* @throws IOException if the creation of the symbolic link was unsuccessful for any reason
490495
*/
491-
public void createSymbolicLink(Path target) throws IOException {
496+
public void createSymbolicLink(Path target, SymlinkTargetType type) throws IOException {
492497
checkSameFileSystem(target);
493-
fileSystem.createSymbolicLink(asFragment(), target.asFragment());
498+
fileSystem.createSymbolicLink(asFragment(), target.asFragment(), type);
499+
}
500+
501+
/**
502+
* Creates a symbolic link with the name of the current path, following symbolic links. The
503+
* referent of the created symlink is is the absolute path "target"; it is not possible to create
504+
* relative symbolic links via this method.
505+
*
506+
* @throws IOException if the creation of the symbolic link was unsuccessful for any reason
507+
*/
508+
public void createSymbolicLink(Path target) throws IOException {
509+
createSymbolicLink(target, SymlinkTargetType.UNSPECIFIED);
510+
}
511+
512+
/**
513+
* Creates a symbolic link with the name of the current path, following symbolic links. The
514+
* referent of the created symlink is is the path fragment "target", which may be absolute or
515+
* relative.
516+
*
517+
* <p>The {@code type} argument denotes the file type of the target, if known. Some filesystems
518+
* require this information to correctly create a symlink. This argument may be ignored if the
519+
* target can be observed to exist and is of a different type.
520+
*
521+
* @param type the target file type
522+
* @throws IOException if the creation of the symbolic link was unsuccessful for any reason
523+
*/
524+
public void createSymbolicLink(PathFragment target, SymlinkTargetType type) throws IOException {
525+
fileSystem.createSymbolicLink(asFragment(), target, type);
494526
}
495527

496528
/**
@@ -501,7 +533,7 @@ public void createSymbolicLink(Path target) throws IOException {
501533
* @throws IOException if the creation of the symbolic link was unsuccessful for any reason
502534
*/
503535
public void createSymbolicLink(PathFragment target) throws IOException {
504-
fileSystem.createSymbolicLink(asFragment(), target);
536+
createSymbolicLink(target, SymlinkTargetType.UNSPECIFIED);
505537
}
506538

507539
/**

src/main/java/com/google/devtools/build/lib/vfs/PathTransformingDelegateFileSystem.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,10 @@ protected boolean isSpecialFile(PathFragment path, boolean followSymlinks) {
119119
}
120120

121121
@Override
122-
protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
122+
protected void createSymbolicLink(
123+
PathFragment linkPath, PathFragment targetFragment, SymlinkTargetType type)
123124
throws IOException {
124-
delegateFs.createSymbolicLink(toDelegatePath(linkPath), targetFragment);
125+
delegateFs.createSymbolicLink(toDelegatePath(linkPath), targetFragment, type);
125126
}
126127

127128
@Override

src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ public void createDirectoryAndParents(PathFragment path) throws IOException {
8181
}
8282

8383
@Override
84-
protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
84+
protected void createSymbolicLink(
85+
PathFragment linkPath, PathFragment targetFragment, SymlinkTargetType type)
8586
throws IOException {
8687
throw modificationException();
8788
}

0 commit comments

Comments
 (0)