| // Copyright 2025 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.skyframe; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.devtools.build.lib.actions.ActionInput; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact; | |
| import com.google.devtools.build.lib.actions.FileArtifactValue; | |
| import com.google.devtools.build.lib.actions.FilesetOutputTree; | |
| import com.google.devtools.build.lib.actions.InputMetadataProvider; | |
| import com.google.devtools.build.lib.actions.MissingDepExecException; | |
| import com.google.devtools.build.lib.actions.RunfilesArtifactValue; | |
| import com.google.devtools.build.lib.actions.RunfilesTree; | |
| import com.google.devtools.build.lib.profiler.SilentCloseable; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.skyframe.MemoizingEvaluator; | |
| import com.google.devtools.build.skyframe.SkyFunction; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import java.io.IOException; | |
| import java.util.Map; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import javax.annotation.Nullable; | |
| /** | |
| * An {@link InputMetadataProvider} implementation that requests the metadata of derived artifacts | |
| * from Skyframe and that of source artifacts from the per-build metadata cache. | |
| * | |
| * <p>During input discovery, the action may well legally read scheduling dependencies that are not | |
| * also inputs. Those are not in the regular input metadata provider (doing so would be a | |
| * performance issue), so we need to ask those from Skyframe instead. It's not that problematic | |
| * because they are known to be transitive Skyframe deps, so we can rely on them being present, with | |
| * one exception (see below) that can be handled without much ceremony. | |
| * | |
| * <p>In theory, this would also work for source artifacts. However, the performance ramifications | |
| * of doing that are unknown. | |
| */ | |
| final class SkyframeInputMetadataProvider implements InputMetadataProvider { | |
| // Not static since it uses "env" and "envMonitor". This works out because "env" is always | |
| // updated to the Environment instance from the last restart and SkyframeLookup closes over | |
| // SkyframeInputMetadataProvider and not "env". | |
| private class SkyframeLookup { | |
| private final SkyKey key; | |
| private volatile SkyValue value; | |
| public SkyframeLookup(SkyKey key) { | |
| this.key = key; | |
| this.value = null; | |
| } | |
| private SkyValue tryLookup() throws InterruptedException, MissingDepExecException { | |
| if (value != null) { | |
| return value; | |
| } | |
| // We reuse envMonitor to guard SkyframeLookup.value. It's the simplest: we need a lock for | |
| // "env" that ensures that only one thread calls methods on it and thus a simple | |
| // synchronized" block won't work so if we wanted to guard "value" separately, we'd have to | |
| // have two nested "synchronized" blocks which sounds like more trouble than it's worth. | |
| synchronized (envMonitor) { | |
| // We use .getExistingValue() to spare a Skyframe edge. This is correct because these are | |
| // always transitive dependencies (it's not a property that's inherently true, though, it's | |
| // just how actions that discover inputs happen to be implemented) | |
| SkyValue localValue = evaluator.getExistingValue(key); | |
| if (localValue == null) { | |
| // This can only happen if a transitive dependency was rewound but the re-evaluation | |
| // resulted in an error or the rewinding is in progress. | |
| // | |
| // env is set to null once any missing values are detected. This is a work-around for a | |
| // semi-bug in include scanning where the include scanner might continue processing after | |
| // the action has already ended. This is problematic because lookups against an | |
| // environment crash once the associated action is done. Instead, any subsequent lookups | |
| // throw MissingDepExecException without adding a dependency edge. At worst, this can only | |
| // result in a superfluous restart. | |
| if (env == null) { | |
| throw new MissingDepExecException(); | |
| } | |
| localValue = env.getValue(key); | |
| if (localValue == null) { | |
| env = null; | |
| throw new MissingDepExecException(); | |
| } | |
| // This can happen if the evaluation of "value" finished between the getExistingValue() | |
| // call and the getValue() one. In this case, "value" is good. We move on. | |
| } | |
| value = localValue; | |
| return localValue; | |
| } | |
| } | |
| } | |
| private final MemoizingEvaluator evaluator; | |
| // Non-null while skyframe lookups are being allowed during input discovery. | |
| @Nullable private SkyFunction.Environment env; | |
| private final Object envMonitor; | |
| private final InputMetadataProvider perBuild; | |
| private final PathFragment relativeOutputPath; | |
| private final ConcurrentHashMap<PathFragment, ActionInput> seen; | |
| /** | |
| * A cache so that we don't need to look up any SkyValue twice. | |
| * | |
| * <p>This is necessary because action rewinding means that even though a {@code getValue()} call | |
| * returned the appropriate value alright, subsequent calls with the same {@code SkyKey} may not | |
| * do so. So theoretically, every call to {@link #getInputMetadata(ActionInput)} should be | |
| * prepared to handle a {@code MissingDepExecException}. | |
| * | |
| * <p>Sadly, that's not the case and the invariant we have is that the <b>first</b> call over the | |
| * course of the evaluation of an action with any given {@code ActionInput} handles that case, the | |
| * subsequent ones not necessarily. This cache is there to make sure that that's alright. | |
| */ | |
| private final ConcurrentHashMap<SkyKey, SkyframeLookup> skyframeLookups; | |
| private boolean allowSkyframe; | |
| SkyframeInputMetadataProvider( | |
| MemoizingEvaluator evaluator, InputMetadataProvider perBuild, String relativeOutputPath) { | |
| this.evaluator = evaluator; | |
| this.envMonitor = new Object(); | |
| this.perBuild = perBuild; | |
| this.relativeOutputPath = PathFragment.create(relativeOutputPath); | |
| this.seen = new ConcurrentHashMap<>(); | |
| this.skyframeLookups = new ConcurrentHashMap<>(); | |
| this.allowSkyframe = false; | |
| } | |
| /** | |
| * Allow Skyframe access while the returned closeable is open. | |
| * | |
| * <p>This should only happen during input discovery, so we disallow it everywhere else. | |
| */ | |
| // TODO: b/416449869 - Add test coverage for a new env being set after a skyframe restart. | |
| SilentCloseable withSkyframeAllowed(SkyFunction.Environment env) { | |
| // No need to synchronize with envMonitor here. This is called before input discovery begins, | |
| // and the closeable is called after input discovery ends, so there are no concurrent calls to | |
| // getInputMetadataChecked. | |
| allowSkyframe = true; | |
| this.env = env; | |
| return () -> { | |
| allowSkyframe = false; | |
| this.env = null; | |
| }; | |
| } | |
| @Nullable | |
| @Override | |
| public FileArtifactValue getInputMetadataChecked(ActionInput input) | |
| throws InterruptedException, IOException, MissingDepExecException { | |
| if (!(input instanceof Artifact artifact)) { | |
| if (!input.getExecPath().startsWith(relativeOutputPath)) { | |
| return perBuild.getInputMetadataChecked(input); | |
| } else { | |
| return null; | |
| } | |
| } | |
| if (artifact.isSourceArtifact()) { | |
| return perBuild.getInputMetadataChecked(input); | |
| } | |
| if (!allowSkyframe) { | |
| return null; | |
| } | |
| if (artifact instanceof SpecialArtifact) { | |
| return null; | |
| } | |
| SkyKey key = Artifact.key(artifact); | |
| SkyframeLookup lookup = skyframeLookups.computeIfAbsent(key, SkyframeLookup::new); | |
| SkyValue value = lookup.tryLookup(); | |
| seen.put(artifact.getExecPath(), artifact); | |
| ActionExecutionValue actionExecutionValue = (ActionExecutionValue) value; | |
| return actionExecutionValue.getExistingFileArtifactValue(artifact); | |
| } | |
| @Nullable | |
| @Override | |
| public TreeArtifactValue getTreeMetadata(ActionInput input) { | |
| return null; | |
| } | |
| @Nullable | |
| @Override | |
| public TreeArtifactValue getEnclosingTreeMetadata(PathFragment execPath) { | |
| return null; | |
| } | |
| @Nullable | |
| @Override | |
| public FilesetOutputTree getFileset(ActionInput input) { | |
| return null; | |
| } | |
| @Override | |
| public Map<Artifact, FilesetOutputTree> getFilesets() { | |
| return ImmutableMap.of(); | |
| } | |
| @Nullable | |
| @Override | |
| public RunfilesArtifactValue getRunfilesMetadata(ActionInput input) { | |
| return null; | |
| } | |
| @Override | |
| public ImmutableList<RunfilesTree> getRunfilesTrees() { | |
| return ImmutableList.of(); | |
| } | |
| @Nullable | |
| @Override | |
| public ActionInput getInput(PathFragment execPath) { | |
| ActionInput result = seen.get(execPath); | |
| if (result == null) { | |
| result = perBuild.getInput(execPath); | |
| } | |
| return result; | |
| } | |
| } |