aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/skyframe
diff options
context:
space:
mode:
authorGravatar shahan <shahan@google.com>2018-05-09 08:23:31 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-05-09 08:24:56 -0700
commitb1dd4e3ad80cd978ed6c86c67fac43f6e0b153b1 (patch)
treef6b59440e13199ca3f470a768516c65ab247c4fa /src/main/java/com/google/devtools/build/lib/skyframe
parentc1085c6d8244fb32f29c355f12ce3c27ba9f2776 (diff)
Adds an action-scoped filesystem.
PiperOrigin-RevId: 195973862
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/skyframe')
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java41
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ActionFileSystem.java658
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java35
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java24
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java16
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java39
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java12
8 files changed, 782 insertions, 46 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
index aca1c5c8a6..283f0d9e03 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
@@ -189,6 +189,17 @@ public class ActionExecutionFunction implements SkyFunction, CompletionReceiver
Preconditions.checkState(!state.hasArtifactData(), "%s %s", state, action);
state.inputArtifactData = checkedInputs.first;
state.expandedArtifacts = checkedInputs.second;
+ if (skyframeActionExecutor.usesActionFileSystem()) {
+ state.actionFileSystem =
+ new ActionFileSystem(
+ checkedInputs.first,
+ // TODO(shahan): based on experimentation, it suffices to include only {@link
+ // com.google.devtools.build.lib.rules.cpp.IncludeScannable#getDeclaredIncludeSrcs}.
+ // That may be a smaller set and more efficient but makes this class have an
+ // awkward dependency on something in the cpp package.
+ action.discoversInputs() ? action.getAllowedDerivedInputs() : ImmutableList.of(),
+ action.getOutputs());
+ }
}
ActionExecutionValue result;
@@ -391,14 +402,18 @@ public class ActionExecutionFunction implements SkyFunction, CompletionReceiver
metadataHandler.discardOutputMetadata();
// This may be recreated if we discover inputs.
+ // TODO(shahan): this isn't used when using ActionFileSystem so we can avoid creating some
+ // unused objects.
PerActionFileCache perActionFileCache =
new PerActionFileCache(
state.inputArtifactData, /*missingArtifactsAllowed=*/ action.discoversInputs());
if (action.discoversInputs()) {
if (state.discoveredInputs == null) {
try {
- state.discoveredInputs = skyframeActionExecutor.discoverInputs(action,
- perActionFileCache, metadataHandler, env);
+ state.updateFileSystemContext(env, metadataHandler);
+ state.discoveredInputs =
+ skyframeActionExecutor.discoverInputs(
+ action, perActionFileCache, metadataHandler, env, state.actionFileSystem);
Preconditions.checkState(state.discoveredInputs != null,
"discoverInputs() returned null on action %s", action);
} catch (MissingDepException e) {
@@ -413,6 +428,7 @@ public class ActionExecutionFunction implements SkyFunction, CompletionReceiver
}
perActionFileCache =
new PerActionFileCache(state.inputArtifactData, /*missingArtifactsAllowed=*/ false);
+ state.updateFileSystemInputData();
// Stage 1 finished, let's do stage 2. The stage 1 of input discovery will have added some
// files with addDiscoveredInputs() and then have waited for those files to be available
@@ -430,6 +446,7 @@ public class ActionExecutionFunction implements SkyFunction, CompletionReceiver
}
perActionFileCache =
new PerActionFileCache(state.inputArtifactData, /*missingArtifactsAllowed=*/ false);
+ state.updateFileSystemInputData();
}
metadataHandler =
new ActionMetadataHandler(state.inputArtifactData, action.getOutputs(), tsgm.get());
@@ -460,12 +477,14 @@ public class ActionExecutionFunction implements SkyFunction, CompletionReceiver
filesetMappings.put(actionInput.getExecPath(), filesetValue.getOutputSymlinks());
}
+ state.updateFileSystemContext(env, metadataHandler);
try (ActionExecutionContext actionExecutionContext =
skyframeActionExecutor.getContext(
perActionFileCache,
metadataHandler,
Collections.unmodifiableMap(state.expandedArtifacts),
- filesetMappings.build())) {
+ filesetMappings.build(),
+ state.actionFileSystem)) {
if (!state.hasExecutedAction()) {
state.value =
skyframeActionExecutor.executeAction(
@@ -772,6 +791,7 @@ public class ActionExecutionFunction implements SkyFunction, CompletionReceiver
Iterable<Artifact> discoveredInputs = null;
Iterable<Artifact> discoveredInputsStage2 = null;
ActionExecutionValue value = null;
+ ActionFileSystem actionFileSystem = null;
boolean hasCollectedInputs() {
return allInputs != null;
@@ -793,6 +813,21 @@ public class ActionExecutionFunction implements SkyFunction, CompletionReceiver
return value != null;
}
+ /** Must be called to assign values to the given variables as they change. */
+ void updateFileSystemContext(
+ SkyFunction.Environment env, ActionMetadataHandler metadataHandler) {
+ if (actionFileSystem != null) {
+ actionFileSystem.updateContext(env, metadataHandler::injectOutputData);
+ }
+ }
+
+ /** Propagates {@link inputArtifactData} changes due to input discovery. */
+ void updateFileSystemInputData() {
+ if (actionFileSystem != null) {
+ actionFileSystem.updateInputData(inputArtifactData);
+ }
+ }
+
@Override
public String toString() {
return token + ", " + value + ", " + allInputs + ", " + inputArtifactData + ", "
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionFileSystem.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionFileSystem.java
new file mode 100644
index 0000000000..62e9761abd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionFileSystem.java
@@ -0,0 +1,658 @@
+// Copyright 2018 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 static java.nio.charset.StandardCharsets.US_ASCII;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Streams;
+import com.google.common.io.BaseEncoding;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionInputFileCache;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.FileStateType;
+import com.google.devtools.build.lib.vfs.AbstractFileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.protobuf.ByteString;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * File system for actions.
+ *
+ * <p>This class is thread-safe except that
+ *
+ * <ul>
+ * <li>{@link updateContext} and {@link updateInputData} must be called exclusively of any other
+ * methods.
+ * <li>This class relies on synchronized access to {@link env}. If there are other threads, that
+ * access {@link env}, they must also used synchronized access.
+ * </ul>
+ */
+final class ActionFileSystem extends AbstractFileSystem implements ActionInputFileCache {
+ private static final Logger LOGGER = Logger.getLogger(ActionFileSystem.class.getName());
+
+ /**
+ * Exec root and source roots.
+ *
+ * <p>First entry is exec root. Used to convert paths into exec paths.
+ */
+ private final LinkedHashSet<PathFragment> roots = new LinkedHashSet<>();
+
+ /** exec path → artifact and metadata */
+ private final Map<PathFragment, ArtifactAndMetadata> inputs;
+
+ /** exec path → artifact and metadata */
+ private final ImmutableMap<PathFragment, ArtifactAndMutableMetadata> outputs;
+
+ /** digest → artifacts in {@link inputs} */
+ private final ConcurrentHashMap<ByteString, Artifact> reverseMap;
+
+ /** Used to lookup metadata for optional inputs. */
+ private SkyFunction.Environment env = null;
+
+ /**
+ * Called whenever there is new metadata for an output.
+ *
+ * <p>This is backed by injection into an {@link ActionMetadataHandler} instance so should only be
+ * called once per artifact.
+ */
+ private MetadataConsumer metadataConsumer = null;
+
+ ActionFileSystem(
+ Map<Artifact, FileArtifactValue> inputData,
+ Iterable<Artifact> allowedInputs,
+ Iterable<Artifact> outputArtifacts) {
+ roots.add(computeExecRoot(outputArtifacts));
+
+ // TODO(shahan): Underestimates because this doesn't account for discovered inputs. Improve
+ // this estimate using data.
+ this.reverseMap = new ConcurrentHashMap<>(inputData.size());
+
+ HashMap<PathFragment, ArtifactAndMetadata> inputs = new HashMap<>();
+ for (Map.Entry<Artifact, FileArtifactValue> entry : inputData.entrySet()) {
+ Artifact input = entry.getKey();
+ updateRootsIfSource(input);
+ inputs.put(input.getExecPath(), new SimpleArtifactAndMetadata(input, entry.getValue()));
+ updateReverseMapIfDigestExists(entry.getValue(), entry.getKey());
+ }
+ for (Artifact input : allowedInputs) {
+ PathFragment execPath = input.getExecPath();
+ inputs.computeIfAbsent(execPath, unused -> new OptionalInputArtifactAndMetadata(input));
+ updateRootsIfSource(input);
+ }
+ this.inputs = inputs;
+
+ validateRoots();
+
+ this.outputs =
+ Streams.stream(outputArtifacts)
+ .collect(
+ ImmutableMap.toImmutableMap(
+ a -> a.getExecPath(), a -> new ArtifactAndMutableMetadata(a)));
+ }
+
+ /**
+ * Must be called prior to access and updated as needed.
+ *
+ * <p>These cannot be passed into the constructor because while {@link ActionFileSystem} is
+ * action-scoped, the environment and metadata consumer change multiple times, at well defined
+ * points, during the lifetime of an action.
+ */
+ public void updateContext(SkyFunction.Environment env, MetadataConsumer metadataConsumer) {
+ this.env = env;
+ this.metadataConsumer = metadataConsumer;
+ }
+
+ /** Input discovery changes the values of the input data map so it must be updated accordingly. */
+ public void updateInputData(Map<Artifact, FileArtifactValue> inputData) {
+ boolean foundNewRoots = false;
+ for (Map.Entry<Artifact, FileArtifactValue> entry : inputData.entrySet()) {
+ ArtifactAndMetadata current = inputs.get(entry.getKey().getExecPath());
+ if (current == null || isUnsetOptional(current)) {
+ Artifact input = entry.getKey();
+ inputs.put(input.getExecPath(), new SimpleArtifactAndMetadata(input, entry.getValue()));
+ foundNewRoots = updateRootsIfSource(entry.getKey()) || foundNewRoots;
+ updateReverseMapIfDigestExists(entry.getValue(), entry.getKey());
+ }
+ }
+ if (foundNewRoots) {
+ validateRoots();
+ }
+ }
+
+ // -------------------- ActionInputFileCache implementation --------------------
+
+ @Override
+ @Nullable
+ public FileArtifactValue getMetadata(ActionInput actionInput) {
+ return apply(
+ actionInput.getExecPath(),
+ input -> {
+ try {
+ return input.getMetadata();
+ } catch (IOException e) {
+ // TODO(shahan): improve the handling of this error by propagating it correctly
+ // through MetadataHandler.getMetadata().
+ throw new IllegalStateException(e);
+ }
+ },
+ output -> output.getMetadata(),
+ () -> null);
+ }
+
+ @Override
+ public boolean contentsAvailableLocally(ByteString digest) {
+ // TODO(shahan): we assume this is never true, though the digests might be present. Should
+ // this be relaxed for locally available source files?
+ return false;
+ }
+
+ @Override
+ @Nullable
+ public Artifact getInputFromDigest(ByteString digest) {
+ return reverseMap.get(digest);
+ }
+
+ @Override
+ public Path getInputPath(ActionInput actionInput) {
+ ArtifactAndMetadata input = inputs.get(actionInput.getExecPath());
+ if (input != null) {
+ return getPath(input.getArtifact().getPath().getPathString());
+ }
+ ArtifactAndMutableMetadata output = outputs.get(actionInput.getExecPath());
+ if (output != null) {
+ return getPath(output.getArtifact().getPath().getPathString());
+ }
+ // TODO(shahan): this might need to be relaxed
+ throw new IllegalStateException(actionInput + " not found");
+ }
+
+ // -------------------- FileSystem implementation --------------------
+
+ @Override
+ public boolean supportsModifications(Path path) {
+ return isOutput(path);
+ }
+
+ @Override
+ public boolean supportsSymbolicLinksNatively(Path path) {
+ return isOutput(path);
+ }
+
+ @Override
+ protected boolean supportsHardLinksNatively(Path path) {
+ return isOutput(path);
+ }
+
+ @Override
+ public boolean isFilePathCaseSensitive() {
+ return true;
+ }
+
+ /** ActionFileSystem currently doesn't track directories. */
+ @Override
+ public boolean createDirectory(Path path) throws IOException {
+ return true;
+ }
+
+ @Override
+ public void createDirectoryAndParents(Path path) throws IOException {}
+
+ @Override
+ protected long getFileSize(Path path, boolean followSymlinks) throws IOException {
+ Preconditions.checkArgument(
+ followSymlinks, "ActionFileSystem doesn't support no-follow: %s", path);
+ return getMetadataOrThrowFileNotFound(path).getSize();
+ }
+
+ @Override
+ public boolean delete(Path path) throws IOException {
+ throw new UnsupportedOperationException(path.getPathString());
+ }
+
+ @Override
+ protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException {
+ Preconditions.checkArgument(
+ followSymlinks, "ActionFileSystem doesn't support no-follow: %s", path);
+ return getMetadataOrThrowFileNotFound(path).getModifiedTime();
+ }
+
+ @Override
+ public void setLastModifiedTime(Path path, long newTime) throws IOException {
+ throw new UnsupportedOperationException(path.getPathString());
+ }
+
+ @Override
+ protected byte[] getFastDigest(Path path, HashFunction hash) throws IOException {
+ if (hash != HashFunction.MD5) {
+ return null;
+ }
+ return getMetadataOrThrowFileNotFound(path).getDigest();
+ }
+
+ @Override
+ protected boolean isSymbolicLink(Path path) {
+ throw new UnsupportedOperationException(path.getPathString());
+ }
+
+ @Override
+ protected boolean isDirectory(Path path, boolean followSymlinks) {
+ Preconditions.checkArgument(
+ followSymlinks, "ActionFileSystem doesn't support no-follow: %s", path);
+ FileArtifactValue metadata = getMetadataUnchecked(path);
+ return metadata == null ? false : metadata.getType() == FileStateType.DIRECTORY;
+ }
+
+ @Override
+ protected boolean isFile(Path path, boolean followSymlinks) {
+ Preconditions.checkArgument(
+ followSymlinks, "ActionFileSystem doesn't support no-follow: %s", path);
+ FileArtifactValue metadata = getMetadataUnchecked(path);
+ return metadata == null ? false : metadata.getType() == FileStateType.REGULAR_FILE;
+ }
+
+ @Override
+ protected boolean isSpecialFile(Path path, boolean followSymlinks) {
+ Preconditions.checkArgument(
+ followSymlinks, "ActionFileSystem doesn't support no-follow: %s", path);
+ FileArtifactValue metadata = getMetadataUnchecked(path);
+ return metadata == null ? false : metadata.getType() == FileStateType.SPECIAL_FILE;
+ }
+
+ private static String createSymbolicLinkErrorMessage(
+ Path linkPath, PathFragment targetFragment, String message) {
+ return "createSymbolicLink(" + linkPath + ", " + targetFragment + "): " + message;
+ }
+
+ @Override
+ protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException {
+ ArtifactAndMetadata input = inputs.get(asExecPath(targetFragment));
+ if (input == null) {
+ throw new FileNotFoundException(
+ createSymbolicLinkErrorMessage(
+ linkPath, targetFragment, targetFragment + " is not an input."));
+ }
+ ArtifactAndMutableMetadata output = outputs.get(asExecPath(linkPath));
+ if (output == null) {
+ throw new FileNotFoundException(
+ createSymbolicLinkErrorMessage(
+ linkPath, targetFragment, linkPath + " is not an output."));
+ }
+ output.setMetadata(input.getMetadata());
+ }
+
+ @Override
+ protected PathFragment readSymbolicLink(Path path) throws IOException {
+ throw new UnsupportedOperationException(path.getPathString());
+ }
+
+ @Override
+ protected boolean exists(Path path, boolean followSymlinks) {
+ Preconditions.checkArgument(
+ followSymlinks, "ActionFileSystem doesn't support no-follow: %s", path);
+ return apply(path, input -> true, output -> output.getMetadata() != null, () -> false);
+ }
+
+ @Override
+ protected Collection<String> getDirectoryEntries(Path path) throws IOException {
+ throw new UnsupportedOperationException(path.getPathString());
+ }
+
+ @Override
+ protected boolean isReadable(Path path) throws IOException {
+ return exists(path, true);
+ }
+
+ @Override
+ protected void setReadable(Path path, boolean readable) throws IOException {}
+
+ @Override
+ protected boolean isWritable(Path path) throws IOException {
+ return isOutput(path);
+ }
+
+ @Override
+ public void setWritable(Path path, boolean writable) throws IOException {}
+
+ @Override
+ protected boolean isExecutable(Path path) throws IOException {
+ return true;
+ }
+
+ @Override
+ protected void setExecutable(Path path, boolean executable) throws IOException {}
+
+ @Override
+ protected InputStream getInputStream(Path path) throws IOException {
+ // TODO(shahan): cleanup callers of this method and disable or maybe figure out a reasonable
+ // implementation.
+ LOGGER.severe("Raw read of path: " + path);
+ return super.getInputStream(path);
+ }
+
+ @Override
+ protected OutputStream getOutputStream(Path path, boolean append) throws IOException {
+ // TODO(shahan): cleanup callers of this method and disable or maybe figure out a reasonable
+ // implementation.
+ LOGGER.severe("Raw write of path: " + path);
+ return super.getOutputStream(path, append);
+ }
+
+ @Override
+ public void renameTo(Path sourcePath, Path targetPath) throws IOException {
+ throw new UnsupportedOperationException("renameTo(" + sourcePath + ", " + targetPath + ")");
+ }
+
+ @Override
+ protected void createFSDependentHardLink(Path linkPath, Path originalPath) throws IOException {
+ throw new UnsupportedOperationException(
+ "createFSDependendHardLink(" + linkPath + ", " + originalPath + ")");
+ }
+
+ // -------------------- Implementation Helpers --------------------
+
+ private PathFragment asExecPath(Path path) {
+ return asExecPath(path.asFragment());
+ }
+
+ private PathFragment asExecPath(PathFragment fragment) {
+ for (PathFragment root : roots) {
+ if (fragment.startsWith(root)) {
+ return fragment.relativeTo(root);
+ }
+ }
+ throw new IllegalArgumentException(fragment + " was not found under any known root: " + roots);
+ }
+
+ private boolean isOutput(Path path) {
+ return outputs.containsKey(asExecPath(path));
+ }
+
+ /**
+ * Lambda-based case implementation.
+ *
+ * <p>One of {@code inputOp} or {@code outputOp} will be called depending on whether {@code path}
+ * is an input or output.
+ */
+ private <T> T apply(Path path, InputFileOperator<T> inputOp, OutputFileOperator<T> outputOp)
+ throws IOException {
+ PathFragment execPath = asExecPath(path);
+ ArtifactAndMetadata input = inputs.get(execPath);
+ if (input != null) {
+ return inputOp.apply(input);
+ }
+ ArtifactAndMutableMetadata output = outputs.get(execPath);
+ if (output != null) {
+ return outputOp.apply(output);
+ }
+ throw new FileNotFoundException(path.getPathString());
+ }
+
+ /**
+ * Apply variant that doesn't throw exceptions.
+ *
+ * <p>Useful for implementing existence-type methods.
+ */
+ private <T> T apply(
+ Path path,
+ Function<ArtifactAndMetadata, T> inputOp,
+ Function<ArtifactAndMutableMetadata, T> outputOp,
+ Supplier<T> notFoundOp) {
+ return apply(asExecPath(path), inputOp, outputOp, notFoundOp);
+ }
+
+ private <T> T apply(
+ PathFragment execPath,
+ Function<ArtifactAndMetadata, T> inputOp,
+ Function<ArtifactAndMutableMetadata, T> outputOp,
+ Supplier<T> notFoundOp) {
+ ArtifactAndMetadata input = inputs.get(execPath);
+ if (input != null) {
+ return inputOp.apply(input);
+ }
+ ArtifactAndMutableMetadata output = outputs.get(execPath);
+ if (output != null) {
+ return outputOp.apply(output);
+ }
+ return notFoundOp.get();
+ }
+
+ private boolean updateRootsIfSource(Artifact input) {
+ if (input.isSourceArtifact()) {
+ return roots.add(input.getRoot().getRoot().asPath().asFragment());
+ }
+ return false;
+ }
+
+ /**
+ * The execution root is globally unique for a build so can be derived from any output.
+ *
+ * <p>Outputs must be nonempty.
+ */
+ private static PathFragment computeExecRoot(Iterable<Artifact> outputs) {
+ Artifact derived = outputs.iterator().next();
+ Preconditions.checkArgument(!derived.isSourceArtifact(), derived);
+ PathFragment rootFragment = derived.getRoot().getRoot().asPath().asFragment();
+ int rootSegments = rootFragment.segmentCount();
+ int execSegments = derived.getRoot().getExecPath().segmentCount();
+ return rootFragment.subFragment(0, rootSegments - execSegments);
+ }
+
+ /**
+ * Verifies that no root is the prefix of any other root.
+ *
+ * <p>TODO(shahan): if this is insufficiently general, we can topologically order on the prefix
+ * relation between roots.
+ */
+ private void validateRoots() {
+ for (PathFragment root1 : roots) {
+ for (PathFragment root2 : roots) {
+ if (root1 == root2) {
+ continue;
+ }
+ Preconditions.checkState(!root1.startsWith(root2), "%s starts with %s", root1, root2);
+ }
+ }
+ }
+
+ private static ByteString toByteString(byte[] digest) {
+ return ByteString.copyFrom(BaseEncoding.base16().lowerCase().encode(digest).getBytes(US_ASCII));
+ }
+
+ private static boolean isUnsetOptional(ArtifactAndMetadata input) {
+ if (input instanceof OptionalInputArtifactAndMetadata) {
+ OptionalInputArtifactAndMetadata optional = (OptionalInputArtifactAndMetadata) input;
+ return !optional.hasMetadata();
+ }
+ return false;
+ }
+
+ private void updateReverseMapIfDigestExists(FileArtifactValue metadata, Artifact artifact) {
+ if (metadata.getDigest() != null) {
+ reverseMap.put(toByteString(metadata.getDigest()), artifact);
+ }
+ }
+
+ private FileArtifactValue getMetadataOrThrowFileNotFound(Path path) throws IOException {
+ return apply(
+ path,
+ input -> input.getMetadata(),
+ output -> {
+ if (output.getMetadata() == null) {
+ throw new FileNotFoundException(path.getPathString());
+ }
+ return output.getMetadata();
+ });
+ }
+
+ @Nullable
+ private FileArtifactValue getMetadataUnchecked(Path path) {
+ return apply(
+ path,
+ input -> {
+ try {
+ return input.getMetadata();
+ } catch (IOException e) {
+ // TODO(shahan): propagate this error correctly through higher level APIs.
+ throw new IllegalStateException(e);
+ }
+ },
+ output -> output.getMetadata(),
+ () -> null);
+ }
+
+ @FunctionalInterface
+ private static interface InputFileOperator<T> {
+ T apply(ArtifactAndMetadata entry) throws IOException;
+ }
+
+ @FunctionalInterface
+ private static interface OutputFileOperator<T> {
+ T apply(ArtifactAndMutableMetadata entry) throws IOException;
+ }
+
+ @FunctionalInterface
+ public static interface MetadataConsumer {
+ void accept(Artifact artifact, FileArtifactValue value) throws IOException;
+ }
+
+ private abstract static class ArtifactAndMetadata {
+ public abstract Artifact getArtifact();
+
+ public abstract FileArtifactValue getMetadata() throws IOException;
+
+ @Override
+ public String toString() {
+ String metadataText = null;
+ try {
+ metadataText = "" + getMetadata();
+ } catch (IOException e) {
+ metadataText = "Error getting metadata(" + e.getMessage() + ")";
+ }
+ return getArtifact() + ": " + metadataText;
+ }
+ }
+
+ private static class SimpleArtifactAndMetadata extends ArtifactAndMetadata {
+ private final Artifact artifact;
+ private final FileArtifactValue metadata;
+
+ private SimpleArtifactAndMetadata(Artifact artifact, FileArtifactValue metadata) {
+ this.artifact = artifact;
+ this.metadata = metadata;
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ @Override
+ public FileArtifactValue getMetadata() {
+ return metadata;
+ }
+ }
+
+ private class OptionalInputArtifactAndMetadata extends ArtifactAndMetadata {
+ private final Artifact artifact;
+ private volatile FileArtifactValue metadata = null;
+
+ private OptionalInputArtifactAndMetadata(Artifact artifact) {
+ this.artifact = artifact;
+ }
+
+ @Override
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ @Override
+ public FileArtifactValue getMetadata() throws IOException {
+ if (metadata == null) {
+ synchronized (this) {
+ if (metadata == null) {
+ try {
+ // TODO(shahan): {@link SkyFunction.Environment} requires single-threaded access so
+ // we enforce that here by making these (multithreaded) calls synchronized. It might
+ // be better to make the underlying methods synchronized to avoid having another
+ // caller unintentionally calling into the environment without locking.
+ //
+ // This is currently known to be reached from the distributor during remote include
+ // scanning. It might make sense to instead of bubbling this error out all the way
+ // from within the distributor, to ensure that this metadata value exists when
+ // creating the spawn from the include parser, which will require slightly fewer
+ // layers of error propagation and there is some batching opportunity (across the
+ // parallel expansion of the include scanner).
+ synchronized (env) {
+ metadata = (FileArtifactValue) env.getValue(ArtifactSkyKey.key(artifact, false));
+ }
+ } catch (InterruptedException e) {
+ throw new InterruptedIOException(e.getMessage());
+ }
+ if (metadata == null) {
+ throw new ActionExecutionFunction.MissingDepException();
+ }
+ updateReverseMapIfDigestExists(metadata, artifact);
+ }
+ }
+ }
+ return metadata;
+ }
+
+ public boolean hasMetadata() {
+ return metadata != null;
+ }
+ }
+
+ private class ArtifactAndMutableMetadata extends ArtifactAndMetadata {
+ private final Artifact artifact;
+ @Nullable private volatile FileArtifactValue metadata = null;
+
+ @Override
+ public Artifact getArtifact() {
+ return artifact;
+ }
+
+ @Override
+ @Nullable
+ public FileArtifactValue getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(FileArtifactValue metadata) throws IOException {
+ metadataConsumer.accept(artifact, metadata);
+ this.metadata = metadata;
+ }
+
+ private ArtifactAndMutableMetadata(Artifact artifact) {
+ this.artifact = artifact;
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
index fe6e8afb61..2b001d981c 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
@@ -167,7 +167,7 @@ public class ActionMetadataHandler implements MetadataHandler {
return null;
}
- return Preconditions.checkNotNull(inputArtifactData.get(input), input);
+ return inputArtifactData.get(input);
}
@Override
@@ -478,36 +478,31 @@ public class ActionMetadataHandler implements MetadataHandler {
size,
locationIndex);
- Preconditions.checkState(injectedFiles.add(output), output);
-
// TODO(shahan): there are a couple of things that could reduce memory usage
// 1. We might be able to skip creating an entry in `outputArtifactData` and only create
// the `FileArtifactValue`, but there are likely downstream consumers that expect it that
// would need to be cleaned up.
// 2. Instead of creating an `additionalOutputData` entry, we could add the extra
// `locationIndex` to `FileStateValue`.
- FileStateValue fileStateValue =
- new FileStateValue.RegularFileStateValue(size, digest, /*contentsProxy=*/ null);
- RootedPath rootedPath =
- RootedPath.toRootedPath(output.getRoot().getRoot(), output.getRootRelativePath());
- FileValue value = FileValue.value(rootedPath, fileStateValue, rootedPath, fileStateValue);
- FileValue oldFsValue = outputArtifactData.putIfAbsent(output, value);
try {
- checkInconsistentData(output, oldFsValue, value);
+ injectOutputData(
+ output,
+ new FileArtifactValue.RemoteFileArtifactValue(digest, size, modifiedTime, locationIndex));
} catch (IOException e) {
- // Should never happen.
- throw new IllegalStateException("Inconsistent FileValues for " + output, e);
+ throw new IllegalStateException(e); // Should never happen.
}
+ }
- FileArtifactValue artifactValue =
- new FileArtifactValue.RemoteFileArtifactValue(digest, size, modifiedTime, locationIndex);
+ public void injectOutputData(Artifact output, FileArtifactValue artifactValue)
+ throws IOException {
+ Preconditions.checkState(injectedFiles.add(output), output);
+ // While `artifactValue` carries the important information, the control flow of `getMetadata`
+ // requires an entry in `outputArtifactData` to access `additionalOutputData`, so a
+ // `PLACEHOLDER` is added to `outputArtifactData`.
+ FileValue oldFileValue = outputArtifactData.putIfAbsent(output, FileValue.PLACEHOLDER);
+ checkInconsistentData(output, oldFileValue, FileValue.PLACEHOLDER);
FileArtifactValue oldArtifactValue = additionalOutputData.putIfAbsent(output, artifactValue);
- try {
- checkInconsistentData(output, oldArtifactValue, artifactValue);
- } catch (IOException e) {
- // Should never happen.
- throw new IllegalStateException("Inconsistent FileArtifactValues for " + output, e);
- }
+ checkInconsistentData(output, oldArtifactValue, artifactValue);
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java
index 46e75cef19..eeb9fc6f91 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FileValue.java
@@ -51,6 +51,16 @@ import javax.annotation.Nullable;
@ThreadSafe
public abstract class FileValue implements SkyValue {
+ /**
+ * Exists to accommodate the control flow of {@link ActionMetadataHandler#getMetadata}.
+ *
+ * <p>{@link ActionMetadataHandler#getMetadata} always checks {@link
+ * ActionMetadataHandler#outputArtifactData} before checking {@link
+ * ActionMetadataHandler#additionalOutputData} so some placeholder value is needed to allow an
+ * injected {@link FileArtifactValue} to be returned.
+ */
+ @AutoCodec public static final FileValue PLACEHOLDER = new PlaceholderFileValue();
+
public boolean exists() {
return realFileStateValue().getType() != FileStateType.NONEXISTENT;
}
@@ -315,4 +325,18 @@ public abstract class FileValue implements SkyValue {
realRootedPath, realFileStateValue, linkTarget);
}
}
+
+ private static final class PlaceholderFileValue extends FileValue {
+ private PlaceholderFileValue() {}
+
+ @Override
+ public RootedPath realRootedPath() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public FileStateValue realFileStateValue() {
+ throw new UnsupportedOperationException();
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
index c02ca2cb1a..e63ae0913d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
@@ -103,6 +103,7 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
+import java.util.function.BooleanSupplier;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -151,7 +152,8 @@ public final class SequencedSkyframeExecutor extends SkyframeExecutor {
List<BuildFileName> buildFilesByPriority,
ActionOnIOExceptionReadingBuildFile actionOnIOExceptionReadingBuildFile,
BuildOptions defaultBuildOptions,
- MutableArtifactFactorySupplier mutableArtifactFactorySupplier) {
+ MutableArtifactFactorySupplier mutableArtifactFactorySupplier,
+ BooleanSupplier usesActionFileSystem) {
super(
evaluatorSupplier,
pkgFactory,
@@ -170,7 +172,8 @@ public final class SequencedSkyframeExecutor extends SkyframeExecutor {
/*shouldUnblockCpuWorkWhenFetchingDeps=*/ false,
defaultBuildOptions,
new PackageProgressReceiver(),
- mutableArtifactFactorySupplier);
+ mutableArtifactFactorySupplier,
+ usesActionFileSystem);
this.diffAwarenessManager = new DiffAwarenessManager(diffAwarenessFactories);
this.customDirtinessCheckers = customDirtinessCheckers;
}
@@ -207,7 +210,8 @@ public final class SequencedSkyframeExecutor extends SkyframeExecutor {
buildFilesByPriority,
actionOnIOExceptionReadingBuildFile,
defaultBuildOptions,
- new MutableArtifactFactorySupplier());
+ new MutableArtifactFactorySupplier(),
+ /*usesActionFileSystem=*/ () -> false);
}
public static SequencedSkyframeExecutor create(
@@ -226,7 +230,8 @@ public final class SequencedSkyframeExecutor extends SkyframeExecutor {
List<BuildFileName> buildFilesByPriority,
ActionOnIOExceptionReadingBuildFile actionOnIOExceptionReadingBuildFile,
BuildOptions defaultBuildOptions,
- MutableArtifactFactorySupplier mutableArtifactFactorySupplier) {
+ MutableArtifactFactorySupplier mutableArtifactFactorySupplier,
+ BooleanSupplier usesActionFileSystem) {
SequencedSkyframeExecutor skyframeExecutor =
new SequencedSkyframeExecutor(
InMemoryMemoizingEvaluator.SUPPLIER,
@@ -245,7 +250,8 @@ public final class SequencedSkyframeExecutor extends SkyframeExecutor {
buildFilesByPriority,
actionOnIOExceptionReadingBuildFile,
defaultBuildOptions,
- mutableArtifactFactorySupplier);
+ mutableArtifactFactorySupplier,
+ usesActionFileSystem);
skyframeExecutor.init();
return skyframeExecutor;
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java
index 27f83013d3..49aea6da6a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java
@@ -64,6 +64,7 @@ public class SequencedSkyframeExecutorFactory implements SkyframeExecutorFactory
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY,
BazelSkyframeExecutorConstants.ACTION_ON_IO_EXCEPTION_READING_BUILD_FILE,
defaultBuildOptions,
- new MutableArtifactFactorySupplier());
+ new MutableArtifactFactorySupplier(),
+ /*usesActionFileSystem=*/ () -> false);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
index 221fc9206d..37683f3d8b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
@@ -32,7 +32,6 @@ import com.google.devtools.build.lib.actions.ActionContext;
import com.google.devtools.build.lib.actions.ActionExecutedEvent;
import com.google.devtools.build.lib.actions.ActionExecutedEvent.ErrorTiming;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
-import com.google.devtools.build.lib.actions.ActionExecutionContextFactory;
import com.google.devtools.build.lib.actions.ActionExecutionException;
import com.google.devtools.build.lib.actions.ActionExecutionStatusReporter;
import com.google.devtools.build.lib.actions.ActionGraph;
@@ -104,6 +103,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
+import java.util.function.BooleanSupplier;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -111,7 +111,7 @@ import javax.annotation.Nullable;
* Action executor: takes care of preparing an action for execution, executing it, validating that
* all output artifacts were created, error reporting, etc.
*/
-public final class SkyframeActionExecutor implements ActionExecutionContextFactory {
+public final class SkyframeActionExecutor {
private static final Logger logger = Logger.getLogger(SkyframeActionExecutor.class.getName());
// Used to prevent check-then-act races in #createOutputDirectories. See the comment there for
@@ -157,12 +157,15 @@ public final class SkyframeActionExecutor implements ActionExecutionContextFacto
private final AtomicReference<ActionExecutionStatusReporter> statusReporterRef;
private OutputService outputService;
+ private final BooleanSupplier usesActionFileSystem;
SkyframeActionExecutor(
ActionKeyContext actionKeyContext,
- AtomicReference<ActionExecutionStatusReporter> statusReporterRef) {
+ AtomicReference<ActionExecutionStatusReporter> statusReporterRef,
+ BooleanSupplier usesActionFileSystem) {
this.actionKeyContext = actionKeyContext;
this.statusReporterRef = statusReporterRef;
+ this.usesActionFileSystem = usesActionFileSystem;
}
/**
@@ -356,6 +359,10 @@ public final class SkyframeActionExecutor implements ActionExecutionContextFacto
this.clientEnv = clientEnv;
}
+ public boolean usesActionFileSystem() {
+ return usesActionFileSystem.getAsBoolean();
+ }
+
void executionOver() {
this.reporter = null;
// This transitively holds a bunch of heavy objects, so it's important to clear it at the
@@ -481,23 +488,24 @@ public final class SkyframeActionExecutor implements ActionExecutionContextFacto
* pass the returned context to {@link #executeAction}, and any other method that needs to execute
* tasks related to that action.
*/
- @Override
public ActionExecutionContext getContext(
ActionInputFileCache graphFileCache,
MetadataHandler metadataHandler,
Map<Artifact, Collection<Artifact>> expandedInputs,
- ImmutableMap<PathFragment, ImmutableList<FilesetOutputSymlink>> inputFilesetMappings) {
+ ImmutableMap<PathFragment, ImmutableList<FilesetOutputSymlink>> inputFilesetMappings,
+ @Nullable ActionFileSystem actionFileSystem) {
FileOutErr fileOutErr = actionLogBufferPathGenerator.generate();
return new ActionExecutionContext(
executorEngine,
- new DelegatingPairFileCache(graphFileCache, perBuildFileCache),
+ createFileCache(graphFileCache, actionFileSystem),
actionInputPrefetcher,
actionKeyContext,
metadataHandler,
fileOutErr,
clientEnv,
inputFilesetMappings,
- new ArtifactExpanderImpl(expandedInputs));
+ new ArtifactExpanderImpl(expandedInputs),
+ actionFileSystem);
}
/**
@@ -606,18 +614,21 @@ public final class SkyframeActionExecutor implements ActionExecutionContextFacto
Action action,
PerActionFileCache graphFileCache,
MetadataHandler metadataHandler,
- Environment env)
+ Environment env,
+ @Nullable ActionFileSystem actionFileSystem)
throws ActionExecutionException, InterruptedException {
+ ActionInputFileCache cache;
ActionExecutionContext actionExecutionContext =
ActionExecutionContext.forInputDiscovery(
executorEngine,
- new DelegatingPairFileCache(graphFileCache, perBuildFileCache),
+ createFileCache(graphFileCache, actionFileSystem),
actionInputPrefetcher,
actionKeyContext,
metadataHandler,
actionLogBufferPathGenerator.generate(),
clientEnv,
- env);
+ env,
+ actionFileSystem);
try {
actionExecutionContext.getEventBus().post(ActionStatusMessage.analysisStrategy(action));
return action.discoverInputs(actionExecutionContext);
@@ -631,6 +642,14 @@ public final class SkyframeActionExecutor implements ActionExecutionContextFacto
}
}
+ private ActionInputFileCache createFileCache(
+ ActionInputFileCache graphFileCache, @Nullable ActionFileSystem actionFileSystem) {
+ if (actionFileSystem != null) {
+ return actionFileSystem;
+ }
+ return new DelegatingPairFileCache(graphFileCache, perBuildFileCache);
+ }
+
/**
* This method should be called if the builder encounters an error during
* execution. This allows the builder to record that it encountered at
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index d05b137b5f..2da54f502e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -38,7 +38,6 @@ import com.google.common.collect.Range;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
import com.google.devtools.build.lib.actions.ActionCacheChecker;
-import com.google.devtools.build.lib.actions.ActionExecutionContextFactory;
import com.google.devtools.build.lib.actions.ActionExecutionStatusReporter;
import com.google.devtools.build.lib.actions.ActionGraph;
import com.google.devtools.build.lib.actions.ActionInputFileCache;
@@ -180,6 +179,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BooleanSupplier;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -342,7 +342,8 @@ public abstract class SkyframeExecutor implements WalkableGraphFactory {
boolean shouldUnblockCpuWorkWhenFetchingDeps,
BuildOptions defaultBuildOptions,
@Nullable PackageProgressReceiver packageProgress,
- MutableArtifactFactorySupplier artifactResolverSupplier) {
+ MutableArtifactFactorySupplier artifactResolverSupplier,
+ BooleanSupplier usesActionFileSystem) {
// Strictly speaking, these arguments are not required for initialization, but all current
// callsites have them at hand, so we might as well set them during construction.
this.evaluatorSupplier = evaluatorSupplier;
@@ -354,7 +355,8 @@ public abstract class SkyframeExecutor implements WalkableGraphFactory {
new SkyframePackageLoader(), new SkyframeTransitivePackageLoader(),
syscalls, cyclesReporter, pkgLocator, numPackagesLoaded, this);
this.resourceManager = ResourceManager.instance();
- this.skyframeActionExecutor = new SkyframeActionExecutor(actionKeyContext, statusReporterRef);
+ this.skyframeActionExecutor =
+ new SkyframeActionExecutor(actionKeyContext, statusReporterRef, usesActionFileSystem);
this.fileSystem = fileSystem;
this.directories = Preconditions.checkNotNull(directories);
this.actionKeyContext = Preconditions.checkNotNull(actionKeyContext);
@@ -977,10 +979,6 @@ public abstract class SkyframeExecutor implements WalkableGraphFactory {
return eventBus.get();
}
- public ActionExecutionContextFactory getActionExecutionContextFactory() {
- return skyframeActionExecutor;
- }
-
@VisibleForTesting
ImmutableList<Root> getPathEntries() {
return pkgLocator.get().getPathEntries();