@@ -61,10 +61,7 @@ private enum DirectoryState {
61
61
62
62
/**
63
63
* Creates output directories for an in-memory action file system ({@link
64
- * com.google.devtools.build.lib.vfs.OutputService.ActionFileSystemType#inMemoryFileSystem}). The
65
- * action-local filesystem starts empty, so we expect the output directory creation to always
66
- * succeed. There can be no interference from state left behind by prior builds or other actions
67
- * intra-build.
64
+ * com.google.devtools.build.lib.vfs.OutputService.ActionFileSystemType#inMemoryFileSystem}).
68
65
*/
69
66
void createActionFsOutputDirectories (
70
67
ImmutableSet <Artifact > actionOutputs , ArtifactPathResolver artifactPathResolver )
@@ -81,9 +78,13 @@ void createActionFsOutputDirectories(
81
78
if (done .add (outputDir )) {
82
79
try {
83
80
outputDir .createDirectoryAndParents ();
81
+ continue ;
84
82
} catch (IOException e ) {
85
- throw new CreateOutputDirectoryException ( outputDir . asFragment (), e );
83
+ /* Fall through to plan B. */
86
84
}
85
+
86
+ Path rootPath = artifactPathResolver .convertPath (outputFile .getRoot ().getRoot ().asPath ());
87
+ forceCreateDirectoryAndParents (outputDir , rootPath );
87
88
}
88
89
}
89
90
}
@@ -133,71 +134,77 @@ void createOutputDirectories(ImmutableSet<Artifact> actionOutputs)
133
134
}
134
135
135
136
if (done .add (outputDir )) {
137
+ Path rootPath = outputFile .getRoot ().getRoot ().asPath ();
136
138
try {
137
- createAndCheckForSymlinks (outputDir , outputFile );
139
+ createAndCheckForSymlinks (outputDir , rootPath );
138
140
continue ;
139
141
} catch (IOException e ) {
140
142
/* Fall through to plan B. */
141
143
}
142
144
143
- // Possibly some direct ancestors are not directories. In that case, we traverse the
144
- // ancestors downward, deleting any non-directories. This handles the case where a file
145
- // becomes a directory. The traversal is done downward because otherwise we may delete
146
- // files through a symlink in a parent directory. Since Blaze never creates such
147
- // directories within a build, we have no idea where on disk we're actually deleting.
145
+ forceCreateDirectoryAndParents (outputDir , rootPath );
146
+ }
147
+ }
148
+ }
149
+
150
+ void forceCreateDirectoryAndParents (Path outputDir , Path rootPath )
151
+ throws CreateOutputDirectoryException {
152
+ // Possibly some direct ancestors are not directories. In that case, we traverse the
153
+ // ancestors downward, deleting any non-directories. This handles the case where a file
154
+ // becomes a directory. The traversal is done downward because otherwise we may delete
155
+ // files through a symlink in a parent directory. Since Blaze never creates such
156
+ // directories within a build, we have no idea where on disk we're actually deleting.
157
+ //
158
+ // Symlinks should not be followed so in order to clean up symlinks pointing to Fileset
159
+ // outputs from previous builds. See bug [incremental build of Fileset fails if
160
+ // Fileset.out was changed to be a subdirectory of the old value].
161
+ try {
162
+ Path p = rootPath ;
163
+ for (String segment : outputDir .relativeTo (p ).segments ()) {
164
+ p = p .getRelative (segment );
165
+
166
+ // This lock ensures that the only thread that observes a filesystem transition in
167
+ // which the path p first exists and then does not is the thread that calls
168
+ // p.delete() and causes the transition.
169
+ //
170
+ // If it were otherwise, then some thread A could test p.exists(), see that it does,
171
+ // then test p.isDirectory(), see that p isn't a directory (because, say, thread
172
+ // B deleted it), and then call p.delete(). That could result in two different kinds
173
+ // of failures:
174
+ //
175
+ // 1) In the time between when thread A sees that p is not a directory and when thread
176
+ // A calls p.delete(), thread B may reach the call to createDirectoryAndParents
177
+ // and create a directory at p, which thread A then deletes. Thread B would then try
178
+ // adding outputs to the directory it thought was there, and fail.
148
179
//
149
- // Symlinks should not be followed so in order to clean up symlinks pointing to Fileset
150
- // outputs from previous builds. See bug [incremental build of Fileset fails if
151
- // Fileset.out was changed to be a subdirectory of the old value].
180
+ // 2) In the time between when thread A sees that p is not a directory and when thread
181
+ // A calls p.delete(), thread B may create a directory at p, and then either create a
182
+ // subdirectory beneath it or add outputs to it. Then when thread A tries to delete p,
183
+ // it would fail.
184
+ Lock lock = outputDirectoryDeletionLock .get (p );
185
+ lock .lock ();
152
186
try {
153
- Path p = outputFile .getRoot ().getRoot ().asPath ();
154
- for (String segment : outputDir .relativeTo (p ).segments ()) {
155
- p = p .getRelative (segment );
156
-
157
- // This lock ensures that the only thread that observes a filesystem transition in
158
- // which the path p first exists and then does not is the thread that calls
159
- // p.delete() and causes the transition.
160
- //
161
- // If it were otherwise, then some thread A could test p.exists(), see that it does,
162
- // then test p.isDirectory(), see that p isn't a directory (because, say, thread
163
- // B deleted it), and then call p.delete(). That could result in two different kinds
164
- // of failures:
165
- //
166
- // 1) In the time between when thread A sees that p is not a directory and when thread
167
- // A calls p.delete(), thread B may reach the call to createDirectoryAndParents
168
- // and create a directory at p, which thread A then deletes. Thread B would then try
169
- // adding outputs to the directory it thought was there, and fail.
170
- //
171
- // 2) In the time between when thread A sees that p is not a directory and when thread
172
- // A calls p.delete(), thread B may create a directory at p, and then either create a
173
- // subdirectory beneath it or add outputs to it. Then when thread A tries to delete p,
174
- // it would fail.
175
- Lock lock = outputDirectoryDeletionLock .get (p );
176
- lock .lock ();
177
- try {
178
- FileStatus stat = p .statIfFound (Symlinks .NOFOLLOW );
179
- if (stat == null ) {
180
- // Missing entry: Break out and create expected directories.
181
- break ;
182
- }
183
- if (stat .isDirectory ()) {
184
- // If this directory used to be a tree artifact it won't be writable.
185
- p .setWritable (true );
186
- knownDirectories .put (p .asFragment (), DirectoryState .FOUND );
187
- } else {
188
- // p may be a file or symlink (possibly from a Fileset in a previous build).
189
- p .delete (); // throws IOException
190
- break ;
191
- }
192
- } finally {
193
- lock .unlock ();
194
- }
187
+ FileStatus stat = p .statIfFound (Symlinks .NOFOLLOW );
188
+ if (stat == null ) {
189
+ // Missing entry: Break out and create expected directories.
190
+ break ;
195
191
}
196
- outputDir .createDirectoryAndParents ();
197
- } catch (IOException e ) {
198
- throw new CreateOutputDirectoryException (outputDir .asFragment (), e );
192
+ if (stat .isDirectory ()) {
193
+ // If this directory used to be a tree artifact it won't be writable.
194
+ p .setWritable (true );
195
+ knownDirectories .put (p .asFragment (), DirectoryState .FOUND );
196
+ } else {
197
+ // p may be a file or symlink (possibly from a Fileset in a previous build).
198
+ p .delete (); // throws IOException
199
+ break ;
200
+ }
201
+ } finally {
202
+ lock .unlock ();
199
203
}
200
204
}
205
+ outputDir .createDirectoryAndParents ();
206
+ } catch (IOException e ) {
207
+ throw new CreateOutputDirectoryException (outputDir .asFragment (), e );
201
208
}
202
209
}
203
210
@@ -206,8 +213,7 @@ void createOutputDirectories(ImmutableSet<Artifact> actionOutputs)
206
213
* output file. These are all expected to be regular directories. Violations of this expectations
207
214
* can only come from state left behind by previous invocations or external filesystem mutation.
208
215
*/
209
- private void createAndCheckForSymlinks (Path dir , Artifact outputFile ) throws IOException {
210
- Path rootPath = outputFile .getRoot ().getRoot ().asPath ();
216
+ private void createAndCheckForSymlinks (Path dir , Path rootPath ) throws IOException {
211
217
PathFragment root = rootPath .asFragment ();
212
218
213
219
// If the output root has not been created yet, do so now.
0 commit comments