Skip to content

Commit 350e329

Browse files
ShreeM01coeuvrekeertk
authored
[6.1.0]Fix symlink file creation overhead (bazelbuild#17488)
* Fix symlink file creation overhead The cost of symlink action scales with the size of input because Bazel re-calculates the digest of the output by following the symlink in `actuallyCompleteAction` (bazelbuild#14125). However, the re-calculation is redundant because the digest was already computed by Bazel when checking the outputs of the generating action. Bazel should be smart enough to reuse the result. There is a global cache in Bazel for digest computation. Symlink action didn't make use of the cache because it uses the path of symlink as key to look up the cache. This PR changes to use the path of input file (i.e. target path) to query the cache to avoid recalculation. For a large target (700MB), the time for symlink action is reduced from 2000ms to 1ms. Closes bazelbuild#17478. PiperOrigin-RevId: 509524641 Change-Id: Id3c9dc07d68758770c092f6307e2433dad40ba10 * Update ActionMetadataHandler.java * Create OutputPermissions.java --------- Co-authored-by: Chi Wang <[email protected]> Co-authored-by: keertk <[email protected]>
1 parent ee1daaf commit 350e329

File tree

3 files changed

+82
-14
lines changed

3 files changed

+82
-14
lines changed

src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java

+52-14
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static com.google.common.base.Preconditions.checkState;
1919
import static java.util.concurrent.TimeUnit.MINUTES;
2020

21+
import com.google.auto.value.AutoValue;
2122
import com.google.common.base.MoreObjects;
2223
import com.google.common.collect.ImmutableList;
2324
import com.google.common.collect.ImmutableMap;
@@ -505,7 +506,7 @@ private FileArtifactValue constructFileArtifactValue(
505506
throws IOException {
506507
checkState(!artifact.isTreeArtifact(), "%s is a tree artifact", artifact);
507508

508-
FileArtifactValue value =
509+
var statAndValue =
509510
fileArtifactValueFromArtifact(
510511
artifact,
511512
artifactPathResolver,
@@ -515,6 +516,7 @@ private FileArtifactValue constructFileArtifactValue(
515516
// Prevent constant metadata artifacts from notifying the timestamp granularity monitor
516517
// and potentially delaying the build for no reason.
517518
artifact.isConstantMetadata() ? null : tsgm);
519+
var value = statAndValue.fileArtifactValue();
518520

519521
// Ensure that we don't have both an injected digest and a digest from the filesystem.
520522
byte[] fileDigest = value.getDigest();
@@ -561,8 +563,17 @@ private FileArtifactValue constructFileArtifactValue(
561563
if (injectedDigest == null && type.isFile()) {
562564
// We don't have an injected digest and there is no digest in the file value (which attempts a
563565
// fast digest). Manually compute the digest instead.
564-
injectedDigest =
565-
DigestUtils.manuallyComputeDigest(artifactPathResolver.toPath(artifact), value.getSize());
566+
Path path = statAndValue.pathNoFollow();
567+
if (statAndValue.statNoFollow() != null
568+
&& statAndValue.statNoFollow().isSymbolicLink()
569+
&& statAndValue.realPath() != null) {
570+
// If the file is a symlink, we compute the digest using the target path so that it's
571+
// possible to hit the digest cache - we probably already computed the digest for the
572+
// target during previous action execution.
573+
path = statAndValue.realPath();
574+
}
575+
576+
injectedDigest = DigestUtils.manuallyComputeDigest(path, value.getSize());
566577
}
567578
return FileArtifactValue.createFromInjectedDigest(value, injectedDigest);
568579
}
@@ -582,15 +593,37 @@ static FileArtifactValue fileArtifactValueFromArtifact(
582593
@Nullable TimestampGranularityMonitor tsgm)
583594
throws IOException {
584595
return fileArtifactValueFromArtifact(
585-
artifact,
586-
ArtifactPathResolver.IDENTITY,
587-
statNoFollow,
588-
/*digestWillBeInjected=*/ false,
589-
xattrProvider,
590-
tsgm);
596+
artifact,
597+
ArtifactPathResolver.IDENTITY,
598+
statNoFollow,
599+
/* digestWillBeInjected= */ false,
600+
xattrProvider,
601+
tsgm).fileArtifactValue();
602+
}
603+
604+
@AutoValue
605+
abstract static class FileArtifactStatAndValue {
606+
public static FileArtifactStatAndValue create(
607+
Path pathNoFollow,
608+
@Nullable Path realPath,
609+
@Nullable FileStatusWithDigest statNoFollow,
610+
FileArtifactValue fileArtifactValue) {
611+
return new AutoValue_ActionMetadataHandler_FileArtifactStatAndValue(
612+
pathNoFollow, realPath, statNoFollow, fileArtifactValue);
613+
}
614+
615+
public abstract Path pathNoFollow();
616+
617+
@Nullable
618+
public abstract Path realPath();
619+
620+
@Nullable
621+
public abstract FileStatusWithDigest statNoFollow();
622+
623+
public abstract FileArtifactValue fileArtifactValue();
591624
}
592625

593-
private static FileArtifactValue fileArtifactValueFromArtifact(
626+
private static FileArtifactStatAndValue fileArtifactValueFromArtifact(
594627
Artifact artifact,
595628
ArtifactPathResolver artifactPathResolver,
596629
@Nullable FileStatusWithDigest statNoFollow,
@@ -604,7 +637,8 @@ private static FileArtifactValue fileArtifactValueFromArtifact(
604637
// If we expect a symlink, we can readlink it directly and handle errors appropriately - there
605638
// is no need for the stat below.
606639
if (artifact.isSymlink()) {
607-
return FileArtifactValue.createForUnresolvedSymlink(pathNoFollow);
640+
var fileArtifactValue = FileArtifactValue.createForUnresolvedSymlink(pathNoFollow);
641+
return FileArtifactStatAndValue.create(pathNoFollow, /* realPath= */ null, statNoFollow, fileArtifactValue);
608642
}
609643

610644
RootedPath rootedPathNoFollow =
@@ -621,8 +655,11 @@ private static FileArtifactValue fileArtifactValueFromArtifact(
621655
}
622656

623657
if (statNoFollow == null || !statNoFollow.isSymbolicLink()) {
624-
return fileArtifactValueFromStat(
625-
rootedPathNoFollow, statNoFollow, digestWillBeInjected, xattrProvider, tsgm);
658+
var fileArtifactValue =
659+
fileArtifactValueFromStat(
660+
rootedPathNoFollow, statNoFollow, digestWillBeInjected, xattrProvider, tsgm);
661+
return FileArtifactStatAndValue.create(
662+
pathNoFollow, /* realPath= */ null, statNoFollow, fileArtifactValue);
626663
}
627664

628665
// We use FileStatus#isSymbolicLink over Path#isSymbolicLink to avoid the unnecessary stat
@@ -642,8 +679,9 @@ private static FileArtifactValue fileArtifactValueFromArtifact(
642679
// and is a source file (since changes to those are checked separately).
643680
FileStatus realStat = realRootedPath.asPath().statIfFound(Symlinks.NOFOLLOW);
644681
FileStatusWithDigest realStatWithDigest = FileStatusWithDigestAdapter.maybeAdapt(realStat);
645-
return fileArtifactValueFromStat(
682+
var fileArtifactValue = fileArtifactValueFromStat(
646683
realRootedPath, realStatWithDigest, digestWillBeInjected, xattrProvider, tsgm);
684+
return FileArtifactStatAndValue.create(pathNoFollow, realPath, statNoFollow, fileArtifactValue);
647685
}
648686

649687
private static FileArtifactValue fileArtifactValueFromStat(

src/main/java/com/google/devtools/build/lib/skyframe/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ java_library(
515515
"//src/main/java/com/google/devtools/build/lib/util/io",
516516
"//src/main/java/com/google/devtools/build/lib/vfs",
517517
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
518+
"//third_party:auto_value",
518519
"//third_party:flogger",
519520
"//third_party:guava",
520521
"//third_party:jsr305",

src/test/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandlerTest.java

+29
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.google.devtools.build.lib.testutil.Scratch;
4242
import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
4343
import com.google.devtools.build.lib.vfs.DigestHashFunction;
44+
import com.google.devtools.build.lib.vfs.DigestUtils;
4445
import com.google.devtools.build.lib.vfs.Path;
4546
import com.google.devtools.build.lib.vfs.PathFragment;
4647
import com.google.devtools.build.lib.vfs.Root;
@@ -714,4 +715,32 @@ public void fileArtifactValueFromArtifactCompatibleWithGetMetadata_notChanged()
714715
assertThat(fileArtifactValueFromArtifactResult.couldBeModifiedSince(getMetadataResult))
715716
.isFalse();
716717
}
718+
719+
@Test
720+
public void fileArtifactValueForSymlink_readFromCache() throws Exception {
721+
DigestUtils.configureCache(1);
722+
Artifact target =
723+
ActionsTestUtil.createArtifactWithRootRelativePath(
724+
outputRoot, PathFragment.create("bin/target"));
725+
scratch.file(target.getPath().getPathString(), "contents");
726+
Artifact symlink =
727+
ActionsTestUtil.createArtifactWithRootRelativePath(
728+
outputRoot, PathFragment.create("bin/symlink"));
729+
scratch
730+
.getFileSystem()
731+
.getPath(symlink.getPath().getPathString())
732+
.createSymbolicLink(scratch.getFileSystem().getPath(target.getPath().getPathString()));
733+
ActionMetadataHandler handler =
734+
createHandler(
735+
new ActionInputMap(0),
736+
/* forInputDiscovery= */ false,
737+
/* outputs= */ ImmutableSet.of(target, symlink));
738+
var targetMetadata = handler.getMetadata(target);
739+
assertThat(DigestUtils.getCacheStats().hitCount()).isEqualTo(0);
740+
741+
var symlinkMetadata = handler.getMetadata(symlink);
742+
743+
assertThat(symlinkMetadata).isEqualTo(targetMetadata);
744+
assertThat(DigestUtils.getCacheStats().hitCount()).isEqualTo(1);
745+
}
717746
}

0 commit comments

Comments
 (0)