When rewinding actions that produce tree artifacts, invalidate the cached directory creation.
Since the action is to be rewound, it will completely re-execute including `Action#prepare`, which deletes its outputs including tree directories. Invalidate these directories in the `knownDirectories` cache so that they are created again.
This change only affects builds which use rewinding without an in-memory action filesystem.
This will soon be integration tested as part of b/229395817 - in fact, that's how I encountered this bug.
PiperOrigin-RevId: 455250588
Change-Id: I5ca188c95d4528d7fbbfcd108b0b2192f2321d8d
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelper.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelper.java
index 53f9b59..8d2a745 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelper.java
@@ -60,9 +60,11 @@
}
/**
- * Create output directories for an ActionFS. The action-local filesystem starts empty, so we
- * expect the output directory creation to always succeed. There can be no interference from state
- * left behind by prior builds or other actions intra-build.
+ * Creates output directories for an {@link
+ * com.google.devtools.build.lib.vfs.OutputService.ActionFileSystemType#IN_MEMORY_FILE_SYSTEM}.
+ * The action-local filesystem starts empty, so we expect the output directory creation to always
+ * succeed. There can be no interference from state left behind by prior builds or other actions
+ * intra-build.
*/
void createActionFsOutputDirectories(
ImmutableSet<Artifact> actionOutputs, ArtifactPathResolver artifactPathResolver)
@@ -87,6 +89,25 @@
}
/**
+ * Called to invalidate the cached creation of tree artifact directories when an action is going
+ * to be rewound.
+ *
+ * <p>We use {@link #knownDirectories} to only create an output directory once per build. With
+ * rewinding, actions that output tree artifacts need to recreate the directories because they are
+ * deleted as part of the {@link com.google.devtools.build.lib.actions.Action#prepare} step.
+ *
+ * <p>Note that this does not need to be called if using {@link
+ * com.google.devtools.build.lib.vfs.OutputService.ActionFileSystemType#IN_MEMORY_FILE_SYSTEM}.
+ */
+ void invalidateTreeArtifactDirectoryCreation(ImmutableSet<Artifact> actionOutputs) {
+ for (Artifact output : actionOutputs) {
+ if (output.isTreeArtifact()) {
+ knownDirectories.remove(output.getPath().asFragment());
+ }
+ }
+ }
+
+ /**
* Creates output directories, with ancestors, for all action outputs.
*
* <p>For regular file outputs, creates the parent directories, for tree outputs creates the tree
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 5cd3034..5b0610b 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
@@ -390,6 +390,9 @@
actionExecutionState.obsolete(actionLookupData, buildActionMap, ownerlessArtifactWrapper);
}
completedAndResetActions.add(ownerlessArtifactWrapper);
+ if (!actionFileSystemType().inMemoryFileSystem()) {
+ outputDirectoryHelper.invalidateTreeArtifactDirectoryCreation(action.getOutputs());
+ }
}
@Nullable
@@ -412,6 +415,9 @@
if (!lostDiscoveredInputs.isEmpty()) {
lostDiscoveredInputsMap.put(ownerlessArtifactWrapper, lostDiscoveredInputs);
}
+ if (!actionFileSystemType().inMemoryFileSystem()) {
+ outputDirectoryHelper.invalidateTreeArtifactDirectoryCreation(action.getOutputs());
+ }
}
void noteActionEvaluationStarted(ActionLookupData actionLookupData, Action action) {
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelperTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelperTest.java
index 9d21806..a679135 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelperTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ActionOutputDirectoryHelperTest.java
@@ -199,6 +199,25 @@
assertThat(e).hasCauseThat().isSameInstanceAs(injectedException);
}
+ @Test
+ public void invalidateTreeArtifactDirectoryCreation_onlyInvalidatesTreeArtifactDirs()
+ throws Exception {
+ ActionOutputDirectoryHelper outputDirectoryHelper = createActionOutputDirectoryHelper();
+ Artifact regularOutput = createOutput("example/regular/file.txt");
+ SpecialArtifact treeOutput = createTreeOutput("example/tree/tree_dir");
+ ImmutableSet<Artifact> outputs = ImmutableSet.of(regularOutput, treeOutput);
+
+ outputDirectoryHelper.createOutputDirectories(outputs);
+ regularOutput.getPath().getParentDirectory().deleteTree();
+ treeOutput.getPath().deleteTree();
+ outputDirectoryHelper.invalidateTreeArtifactDirectoryCreation(outputs);
+ outputDirectoryHelper.createOutputDirectories(outputs);
+
+ // Only tree artifact directories are recreated.
+ assertThat(regularOutput.getPath().getParentDirectory().exists()).isFalse();
+ assertThat(treeOutput.getPath().exists()).isTrue();
+ }
+
private FileSystem createFileSystemInjectingException(
PathFragment failingPath, IOException injectedException) {
return new DelegateFileSystem(execRoot.getFileSystem()) {