blob: 31fa632225b822beefb163a87dc819f89381a3ca [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.skyframe;
15
tomlua155b532017-11-08 20:12:47 +010016import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017import com.google.common.base.Predicate;
Rumou Duan45e8e572016-06-17 16:43:44 +000018import com.google.common.base.Supplier;
19import com.google.common.base.Suppliers;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.base.Throwables;
21import com.google.common.collect.ImmutableList;
Eric Fellheimere6590722015-11-17 17:07:48 +000022import com.google.common.collect.ImmutableSet;
Rumou Duan45e8e572016-06-17 16:43:44 +000023import com.google.common.collect.ImmutableSortedSet;
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000024import com.google.common.collect.Range;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import com.google.common.collect.Sets;
26import com.google.common.util.concurrent.ThreadFactoryBuilder;
27import com.google.devtools.build.lib.actions.Artifact;
shahan602cc852018-06-06 20:09:57 -070028import com.google.devtools.build.lib.actions.FileValue;
Eric Fellheimer6a9d7e52015-06-18 22:08:32 +000029import com.google.devtools.build.lib.concurrent.ExecutorUtil;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.concurrent.Sharder;
31import com.google.devtools.build.lib.concurrent.ThrowableRecordingRunnableWrapper;
Nathan Harmatae1b61d02015-10-06 00:09:42 +000032import com.google.devtools.build.lib.profiler.AutoProfiler;
33import com.google.devtools.build.lib.profiler.AutoProfiler.ElapsedTimeReceiver;
twerth646dfd12018-07-04 01:58:40 -070034import com.google.devtools.build.lib.profiler.Profiler;
35import com.google.devtools.build.lib.profiler.SilentCloseable;
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +000036import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker.DirtyResult;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import com.google.devtools.build.lib.util.LoggingUtil;
38import com.google.devtools.build.lib.util.Pair;
39import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
40import com.google.devtools.build.lib.vfs.BatchStat;
41import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
Eric Fellheimere6590722015-11-17 17:07:48 +000042import com.google.devtools.build.lib.vfs.ModifiedFileSet;
Michael Thvedte4a7b0792016-02-09 12:15:53 +000043import com.google.devtools.build.lib.vfs.Path;
Eric Fellheimere6590722015-11-17 17:07:48 +000044import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045import com.google.devtools.build.skyframe.Differencer;
janakre54491e2018-07-11 16:29:13 -070046import com.google.devtools.build.skyframe.FunctionHermeticity;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010047import com.google.devtools.build.skyframe.SkyFunctionName;
48import com.google.devtools.build.skyframe.SkyKey;
49import com.google.devtools.build.skyframe.SkyValue;
Nathan Harmata8cd29782015-11-10 03:24:01 +000050import com.google.devtools.build.skyframe.WalkableGraph;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010051import java.io.IOException;
52import java.util.Collection;
53import java.util.Collections;
54import java.util.HashMap;
Nathan Harmata8cd29782015-11-10 03:24:01 +000055import java.util.HashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010056import java.util.List;
57import java.util.Map;
Rumou Duan45e8e572016-06-17 16:43:44 +000058import java.util.NavigableSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010059import java.util.Set;
60import java.util.concurrent.ConcurrentHashMap;
61import java.util.concurrent.ExecutorService;
62import java.util.concurrent.Executors;
cushon4d70fae2017-04-11 01:01:13 +000063import java.util.concurrent.Future;
Nathan Harmatae1b61d02015-10-06 00:09:42 +000064import java.util.concurrent.TimeUnit;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010065import java.util.concurrent.atomic.AtomicInteger;
66import java.util.logging.Level;
67import java.util.logging.Logger;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010068import javax.annotation.Nullable;
69
70/**
71 * A helper class to find dirty values by accessing the filesystem directly (contrast with
72 * {@link DiffAwareness}).
73 */
Nathan Harmata8cd29782015-11-10 03:24:01 +000074public class FilesystemValueChecker {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010075
Nathan Harmata3a509bd2015-10-06 01:00:47 +000076 private static final int DIRTINESS_CHECK_THREADS = 200;
lberki97abb522017-09-04 18:51:57 +020077 private static final Logger logger = Logger.getLogger(FilesystemValueChecker.class.getName());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010078
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010079 private static final Predicate<SkyKey> ACTION_FILTER =
80 SkyFunctionName.functionIs(SkyFunctions.ACTION_EXECUTION);
81
82 private final TimestampGranularityMonitor tsgm;
Nathan Harmata9b38b2c2015-08-27 16:11:07 +000083 @Nullable
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000084 private final Range<Long> lastExecutionTimeRange;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010085 private AtomicInteger modifiedOutputFilesCounter = new AtomicInteger(0);
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000086 private AtomicInteger modifiedOutputFilesIntraBuildCounter = new AtomicInteger(0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010087
Nathan Harmata8cd29782015-11-10 03:24:01 +000088 public FilesystemValueChecker(@Nullable TimestampGranularityMonitor tsgm,
89 @Nullable Range<Long> lastExecutionTimeRange) {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +000090 this.tsgm = tsgm;
91 this.lastExecutionTimeRange = lastExecutionTimeRange;
92 }
93
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010094 /**
Nathan Harmata8cd29782015-11-10 03:24:01 +000095 * Returns a {@link Differencer.DiffWithDelta} containing keys from the give map that are dirty
96 * according to the passed-in {@code dirtinessChecker}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010097 */
Nathan Harmata8cd29782015-11-10 03:24:01 +000098 // TODO(bazel-team): Refactor these methods so that FilesystemValueChecker only operates on a
99 // WalkableGraph.
100 Differencer.DiffWithDelta getDirtyKeys(Map<SkyKey, SkyValue> valuesMap,
101 SkyValueDirtinessChecker dirtinessChecker) throws InterruptedException {
102 return getDirtyValues(new MapBackedValueFetcher(valuesMap), valuesMap.keySet(),
103 dirtinessChecker, /*checkMissingValues=*/false);
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000104 }
105
106 /**
107 * Returns a {@link Differencer.DiffWithDelta} containing keys that are dirty according to the
108 * passed-in {@code dirtinessChecker}.
109 */
Nathan Harmata8cd29782015-11-10 03:24:01 +0000110 public Differencer.DiffWithDelta getNewAndOldValues(Map<SkyKey, SkyValue> valuesMap,
111 Iterable<SkyKey> keys, SkyValueDirtinessChecker dirtinessChecker)
112 throws InterruptedException {
113 return getDirtyValues(new MapBackedValueFetcher(valuesMap), keys,
114 dirtinessChecker, /*checkMissingValues=*/true);
115 }
116
117 /**
118 * Returns a {@link Differencer.DiffWithDelta} containing keys that are dirty according to the
119 * passed-in {@code dirtinessChecker}.
120 */
121 public Differencer.DiffWithDelta getNewAndOldValues(WalkableGraph walkableGraph,
122 Iterable<SkyKey> keys, SkyValueDirtinessChecker dirtinessChecker)
123 throws InterruptedException {
124 return getDirtyValues(new WalkableGraphBackedValueFetcher(walkableGraph), keys,
125 dirtinessChecker, /*checkMissingValues=*/true);
126 }
127
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000128 private interface ValueFetcher {
Nathan Harmata8cd29782015-11-10 03:24:01 +0000129 @Nullable
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000130 SkyValue get(SkyKey key) throws InterruptedException;
Nathan Harmata8cd29782015-11-10 03:24:01 +0000131 }
132
133 private static class WalkableGraphBackedValueFetcher implements ValueFetcher {
134 private final WalkableGraph walkableGraph;
135
136 private WalkableGraphBackedValueFetcher(WalkableGraph walkableGraph) {
137 this.walkableGraph = walkableGraph;
138 }
139
140 @Override
141 @Nullable
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000142 public SkyValue get(SkyKey key) throws InterruptedException {
Janak Ramakrishnan112840b2016-12-29 21:49:56 +0000143 return walkableGraph.getValue(key);
Nathan Harmata8cd29782015-11-10 03:24:01 +0000144 }
145 }
146
147 private static class MapBackedValueFetcher implements ValueFetcher {
148 private final Map<SkyKey, SkyValue> valuesMap;
149
150 private MapBackedValueFetcher(Map<SkyKey, SkyValue> valuesMap) {
151 this.valuesMap = valuesMap;
152 }
153
154 @Override
155 @Nullable
156 public SkyValue get(SkyKey key) {
157 return valuesMap.get(key);
158 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100159 }
160
161 /**
162 * Return a collection of action values which have output files that are not in-sync with
163 * the on-disk file value (were modified externally).
164 */
Nathan Harmata8cd29782015-11-10 03:24:01 +0000165 Collection<SkyKey> getDirtyActionValues(Map<SkyKey, SkyValue> valuesMap,
Eric Fellheimere6590722015-11-17 17:07:48 +0000166 @Nullable final BatchStat batchStatter, ModifiedFileSet modifiedOutputFiles)
167 throws InterruptedException {
168 if (modifiedOutputFiles == ModifiedFileSet.NOTHING_MODIFIED) {
lberki97abb522017-09-04 18:51:57 +0200169 logger.info("Not checking for dirty actions since nothing was modified");
Eric Fellheimere6590722015-11-17 17:07:48 +0000170 return ImmutableList.of();
171 }
lberki97abb522017-09-04 18:51:57 +0200172 logger.info("Accumulating dirty actions");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100173 final int numOutputJobs = Runtime.getRuntime().availableProcessors() * 4;
Nathan Harmata8cd29782015-11-10 03:24:01 +0000174 final Set<SkyKey> actionSkyKeys = new HashSet<>();
twerth646dfd12018-07-04 01:58:40 -0700175 try (SilentCloseable c = Profiler.instance().profile("getDirtyActionValues.filter_actions")) {
176 for (SkyKey key : valuesMap.keySet()) {
177 if (ACTION_FILTER.apply(key)) {
178 actionSkyKeys.add(key);
179 }
Nathan Harmata8cd29782015-11-10 03:24:01 +0000180 }
181 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100182 final Sharder<Pair<SkyKey, ActionExecutionValue>> outputShards =
183 new Sharder<>(numOutputJobs, actionSkyKeys.size());
184
185 for (SkyKey key : actionSkyKeys) {
Nathan Harmata8cd29782015-11-10 03:24:01 +0000186 outputShards.add(Pair.of(key, (ActionExecutionValue) valuesMap.get(key)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100187 }
lberki97abb522017-09-04 18:51:57 +0200188 logger.info("Sharded action values for batching");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100189
190 ExecutorService executor = Executors.newFixedThreadPool(
191 numOutputJobs,
192 new ThreadFactoryBuilder().setNameFormat("FileSystem Output File Invalidator %d").build());
193
194 Collection<SkyKey> dirtyKeys = Sets.newConcurrentHashSet();
195 ThrowableRecordingRunnableWrapper wrapper =
196 new ThrowableRecordingRunnableWrapper("FileSystemValueChecker#getDirtyActionValues");
197
198 modifiedOutputFilesCounter.set(0);
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000199 modifiedOutputFilesIntraBuildCounter.set(0);
Rumou Duan45e8e572016-06-17 16:43:44 +0000200 final ImmutableSet<PathFragment> knownModifiedOutputFiles =
Eric Fellheimere6590722015-11-17 17:07:48 +0000201 modifiedOutputFiles == ModifiedFileSet.EVERYTHING_MODIFIED
202 ? null
203 : modifiedOutputFiles.modifiedSourceFiles();
Rumou Duan45e8e572016-06-17 16:43:44 +0000204
205 // Initialized lazily through a supplier because it is only used to check modified
206 // TreeArtifacts, which are not frequently used in builds.
207 Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles =
208 Suppliers.memoize(new Supplier<NavigableSet<PathFragment>>() {
209 @Override
210 public NavigableSet<PathFragment> get() {
211 if (knownModifiedOutputFiles == null) {
212 return null;
213 } else {
214 return ImmutableSortedSet.copyOf(knownModifiedOutputFiles);
215 }
216 }
217 });
218
twerth646dfd12018-07-04 01:58:40 -0700219 boolean interrupted;
220 try (SilentCloseable c = Profiler.instance().profile("getDirtyActionValues.stat_files")) {
221 for (List<Pair<SkyKey, ActionExecutionValue>> shard : outputShards) {
222 Runnable job =
223 (batchStatter == null)
224 ? outputStatJob(
225 dirtyKeys, shard, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)
226 : batchStatJob(
227 dirtyKeys,
228 shard,
229 batchStatter,
230 knownModifiedOutputFiles,
231 sortedKnownModifiedOutputFiles);
232 Future<?> unused = executor.submit(wrapper.wrap(job));
233 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100234
twerth646dfd12018-07-04 01:58:40 -0700235 interrupted = ExecutorUtil.interruptibleShutdown(executor);
236 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100237 Throwables.propagateIfPossible(wrapper.getFirstThrownError());
lberki97abb522017-09-04 18:51:57 +0200238 logger.info("Completed output file stat checks");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100239 if (interrupted) {
240 throw new InterruptedException();
241 }
242 return dirtyKeys;
243 }
244
245 private Runnable batchStatJob(final Collection<SkyKey> dirtyKeys,
Eric Fellheimere6590722015-11-17 17:07:48 +0000246 final List<Pair<SkyKey, ActionExecutionValue>> shard,
Rumou Duan45e8e572016-06-17 16:43:44 +0000247 final BatchStat batchStatter, final ImmutableSet<PathFragment> knownModifiedOutputFiles,
248 final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100249 return new Runnable() {
250 @Override
251 public void run() {
Rumou Duana77f32c2016-04-13 21:59:21 +0000252 Map<Artifact, Pair<SkyKey, ActionExecutionValue>> fileToKeyAndValue = new HashMap<>();
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000253 Map<Artifact, Pair<SkyKey, ActionExecutionValue>> treeArtifactsToKeyAndValue =
254 new HashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100255 for (Pair<SkyKey, ActionExecutionValue> keyAndValue : shard) {
256 ActionExecutionValue actionValue = keyAndValue.getSecond();
257 if (actionValue == null) {
258 dirtyKeys.add(keyAndValue.getFirst());
259 } else {
Rumou Duana77f32c2016-04-13 21:59:21 +0000260 for (Artifact artifact : actionValue.getAllFileValues().keySet()) {
261 if (shouldCheckFile(knownModifiedOutputFiles, artifact)) {
262 fileToKeyAndValue.put(artifact, keyAndValue);
Eric Fellheimere6590722015-11-17 17:07:48 +0000263 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100264 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000265
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000266 for (Artifact artifact : actionValue.getAllTreeArtifactValues().keySet()) {
Rumou Duan45e8e572016-06-17 16:43:44 +0000267 if (shouldCheckTreeArtifact(sortedKnownModifiedOutputFiles.get(), artifact)) {
268 treeArtifactsToKeyAndValue.put(artifact, keyAndValue);
269 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000270 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100271 }
272 }
273
Rumou Duana77f32c2016-04-13 21:59:21 +0000274 List<Artifact> artifacts = ImmutableList.copyOf(fileToKeyAndValue.keySet());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100275 List<FileStatusWithDigest> stats;
276 try {
lberki97abb522017-09-04 18:51:57 +0200277 stats =
278 batchStatter.batchStat(
279 /*includeDigest=*/ true,
280 /*includeLinks=*/ true,
281 Artifact.asPathFragments(artifacts));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100282 } catch (IOException e) {
283 // Batch stat did not work. Log an exception and fall back on system calls.
284 LoggingUtil.logToRemote(Level.WARNING, "Unable to process batch stat", e);
lberki97abb522017-09-04 18:51:57 +0200285 logger.log(Level.WARNING, "Unable to process batch stat", e);
Rumou Duan45e8e572016-06-17 16:43:44 +0000286 outputStatJob(dirtyKeys, shard, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)
287 .run();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100288 return;
289 } catch (InterruptedException e) {
290 // We handle interrupt in the main thread.
291 return;
292 }
293
lberki97abb522017-09-04 18:51:57 +0200294 Preconditions.checkState(
295 artifacts.size() == stats.size(),
296 "artifacts.size() == %s stats.size() == %s",
297 artifacts.size(),
298 stats.size());
Rumou Duana77f32c2016-04-13 21:59:21 +0000299 for (int i = 0; i < artifacts.size(); i++) {
300 Artifact artifact = artifacts.get(i);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100301 FileStatusWithDigest stat = stats.get(i);
Rumou Duana77f32c2016-04-13 21:59:21 +0000302 Pair<SkyKey, ActionExecutionValue> keyAndValue = fileToKeyAndValue.get(artifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100303 ActionExecutionValue actionValue = keyAndValue.getSecond();
304 SkyKey key = keyAndValue.getFirst();
Rumou Duana77f32c2016-04-13 21:59:21 +0000305 FileValue lastKnownData = actionValue.getAllFileValues().get(artifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100306 try {
lberki97abb522017-09-04 18:51:57 +0200307 FileValue newData = ActionMetadataHandler.fileValueFromArtifact(artifact, stat, tsgm);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100308 if (!newData.equals(lastKnownData)) {
lberki97abb522017-09-04 18:51:57 +0200309 updateIntraBuildModifiedCounter(
310 stat != null ? stat.getLastChangeTime() : -1,
311 lastKnownData.isSymlink(),
312 newData.isSymlink());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100313 modifiedOutputFilesCounter.getAndIncrement();
314 dirtyKeys.add(key);
315 }
316 } catch (IOException e) {
317 // This is an unexpected failure getting a digest or symlink target.
318 modifiedOutputFilesCounter.getAndIncrement();
319 dirtyKeys.add(key);
320 }
321 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000322
323 // Unfortunately, there exists no facility to batch list directories.
324 // We must use direct filesystem calls.
325 for (Map.Entry<Artifact, Pair<SkyKey, ActionExecutionValue>> entry :
326 treeArtifactsToKeyAndValue.entrySet()) {
327 Artifact artifact = entry.getKey();
328 if (treeArtifactIsDirty(
329 entry.getKey(), entry.getValue().getSecond().getTreeArtifactValue(artifact))) {
330 Path path = artifact.getPath();
331 // Count the changed directory as one "file".
332 // TODO(bazel-team): There are no tests for this codepath.
333 try {
lberki97abb522017-09-04 18:51:57 +0200334 updateIntraBuildModifiedCounter(
335 path.exists() ? path.getLastModifiedTime() : -1, false, path.isSymbolicLink());
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000336 } catch (IOException e) {
337 // Do nothing here.
338 }
339
340 modifiedOutputFilesCounter.getAndIncrement();
341 dirtyKeys.add(entry.getValue().getFirst());
342 }
343 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100344 }
345 };
346 }
347
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000348 private void updateIntraBuildModifiedCounter(long time, boolean oldWasSymlink,
Nathan Harmata7a344272015-06-05 20:52:38 +0000349 boolean newIsSymlink) {
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000350 if (lastExecutionTimeRange != null
351 && lastExecutionTimeRange.contains(time)
352 && !(oldWasSymlink && newIsSymlink)) {
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000353 modifiedOutputFilesIntraBuildCounter.incrementAndGet();
354 }
355 }
356
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100357 private Runnable outputStatJob(final Collection<SkyKey> dirtyKeys,
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000358 final List<Pair<SkyKey, ActionExecutionValue>> shard,
Rumou Duan45e8e572016-06-17 16:43:44 +0000359 final ImmutableSet<PathFragment> knownModifiedOutputFiles,
360 final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100361 return new Runnable() {
362 @Override
363 public void run() {
364 for (Pair<SkyKey, ActionExecutionValue> keyAndValue : shard) {
365 ActionExecutionValue value = keyAndValue.getSecond();
Eric Fellheimere6590722015-11-17 17:07:48 +0000366 if (value == null
Rumou Duan45e8e572016-06-17 16:43:44 +0000367 || actionValueIsDirtyWithDirectSystemCalls(
368 value, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100369 dirtyKeys.add(keyAndValue.getFirst());
370 }
371 }
372 }
373 };
374 }
375
376 /**
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000377 * Returns the number of modified output files inside of dirty actions.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100378 */
379 int getNumberOfModifiedOutputFiles() {
380 return modifiedOutputFilesCounter.get();
381 }
382
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000383 /** Returns the number of modified output files that occur during the previous build. */
384 int getNumberOfModifiedOutputFilesDuringPreviousBuild() {
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000385 return modifiedOutputFilesIntraBuildCounter.get();
386 }
387
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000388 private boolean treeArtifactIsDirty(Artifact artifact, TreeArtifactValue value) {
389 if (artifact.getPath().isSymbolicLink()) {
390 // TreeArtifacts may not be symbolic links.
391 return true;
392 }
393
394 // There doesn't appear to be any facility to batch list directories... we must
395 // do things the 'slow' way.
396 try {
felly09efb3f2018-07-26 07:46:15 -0700397 Set<PathFragment> currentDirectoryValue =
398 TreeArtifactValue.explodeDirectory(artifact.getPath());
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000399 Set<PathFragment> valuePaths = value.getChildPaths();
400 return !currentDirectoryValue.equals(valuePaths);
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000401 } catch (IOException e) {
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000402 return true;
403 }
404 }
405
Eric Fellheimere6590722015-11-17 17:07:48 +0000406 private boolean actionValueIsDirtyWithDirectSystemCalls(ActionExecutionValue actionValue,
Rumou Duan45e8e572016-06-17 16:43:44 +0000407 ImmutableSet<PathFragment> knownModifiedOutputFiles,
408 Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100409 boolean isDirty = false;
Rumou Duana77f32c2016-04-13 21:59:21 +0000410 for (Map.Entry<Artifact, FileValue> entry : actionValue.getAllFileValues().entrySet()) {
411 Artifact file = entry.getKey();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100412 FileValue lastKnownData = entry.getValue();
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000413 if (shouldCheckFile(knownModifiedOutputFiles, file)) {
Eric Fellheimere6590722015-11-17 17:07:48 +0000414 try {
Rumou Duana77f32c2016-04-13 21:59:21 +0000415 FileValue fileValue = ActionMetadataHandler.fileValueFromArtifact(file, null,
416 tsgm);
Eric Fellheimere6590722015-11-17 17:07:48 +0000417 if (!fileValue.equals(lastKnownData)) {
418 updateIntraBuildModifiedCounter(fileValue.exists()
419 ? fileValue.realRootedPath().asPath().getLastModifiedTime()
420 : -1, lastKnownData.isSymlink(), fileValue.isSymlink());
421 modifiedOutputFilesCounter.getAndIncrement();
422 isDirty = true;
423 }
424 } catch (IOException e) {
425 // This is an unexpected failure getting a digest or symlink target.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100426 modifiedOutputFilesCounter.getAndIncrement();
427 isDirty = true;
428 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100429 }
430 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000431
432 for (Map.Entry<Artifact, TreeArtifactValue> entry :
433 actionValue.getAllTreeArtifactValues().entrySet()) {
434 Artifact artifact = entry.getKey();
Rumou Duan45e8e572016-06-17 16:43:44 +0000435
436 if (shouldCheckTreeArtifact(sortedKnownModifiedOutputFiles.get(), artifact)
437 && treeArtifactIsDirty(artifact, entry.getValue())) {
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000438 Path path = artifact.getPath();
439 // Count the changed directory as one "file".
440 try {
441 updateIntraBuildModifiedCounter(path.exists()
442 ? path.getLastModifiedTime()
443 : -1, false, path.isSymbolicLink());
444 } catch (IOException e) {
445 // Do nothing here.
446 }
447
448 modifiedOutputFilesCounter.getAndIncrement();
449 isDirty = true;
450 }
451 }
452
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100453 return isDirty;
454 }
455
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000456 private static boolean shouldCheckFile(ImmutableSet<PathFragment> knownModifiedOutputFiles,
Rumou Duana77f32c2016-04-13 21:59:21 +0000457 Artifact artifact) {
Eric Fellheimere6590722015-11-17 17:07:48 +0000458 return knownModifiedOutputFiles == null
Rumou Duana77f32c2016-04-13 21:59:21 +0000459 || knownModifiedOutputFiles.contains(artifact.getExecPath());
Eric Fellheimere6590722015-11-17 17:07:48 +0000460 }
461
Rumou Duan45e8e572016-06-17 16:43:44 +0000462 private static boolean shouldCheckTreeArtifact(
463 @Nullable NavigableSet<PathFragment> knownModifiedOutputFiles, Artifact treeArtifact) {
464 // If null, everything needs to be checked.
465 if (knownModifiedOutputFiles == null) {
466 return true;
467 }
468
469 // Here we do the following to see whether a TreeArtifact is modified:
470 // 1. Sort the set of modified file paths in lexicographical order using TreeSet.
471 // 2. Get the first modified output file path that is greater than or equal to the exec path of
472 // the TreeArtifact to check.
473 // 3. Check whether the returned file path contains the exec path of the TreeArtifact as a
474 // prefix path.
475 PathFragment artifactExecPath = treeArtifact.getExecPath();
476 PathFragment headPath = knownModifiedOutputFiles.ceiling(artifactExecPath);
477
478 return headPath != null && headPath.startsWith(artifactExecPath);
479 }
480
Nathan Harmata8cd29782015-11-10 03:24:01 +0000481 private BatchDirtyResult getDirtyValues(ValueFetcher fetcher,
482 Iterable<SkyKey> keys, final SkyValueDirtinessChecker checker,
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000483 final boolean checkMissingValues) throws InterruptedException {
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000484 ExecutorService executor =
485 Executors.newFixedThreadPool(
486 DIRTINESS_CHECK_THREADS,
487 new ThreadFactoryBuilder().setNameFormat("FileSystem Value Invalidator %d").build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100488
489 final BatchDirtyResult batchResult = new BatchDirtyResult();
490 ThrowableRecordingRunnableWrapper wrapper =
491 new ThrowableRecordingRunnableWrapper("FilesystemValueChecker#getDirtyValues");
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000492 final AtomicInteger numKeysScanned = new AtomicInteger(0);
493 final AtomicInteger numKeysChecked = new AtomicInteger(0);
lberki97abb522017-09-04 18:51:57 +0200494 ElapsedTimeReceiver elapsedTimeReceiver =
fellyb6f86422018-02-20 19:01:35 -0800495 elapsedTimeNanos -> {
496 if (elapsedTimeNanos > 0) {
497 logger.info(
498 String.format(
499 "Spent %d ms checking %d filesystem nodes (%d scanned)",
500 TimeUnit.MILLISECONDS.convert(elapsedTimeNanos, TimeUnit.NANOSECONDS),
501 numKeysChecked.get(),
502 numKeysScanned.get()));
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000503 }
lberki97abb522017-09-04 18:51:57 +0200504 };
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000505 try (AutoProfiler prof = AutoProfiler.create(elapsedTimeReceiver)) {
Nathan Harmata8cd29782015-11-10 03:24:01 +0000506 for (final SkyKey key : keys) {
Nathan Harmata3a509bd2015-10-06 01:00:47 +0000507 numKeysScanned.incrementAndGet();
508 if (!checker.applies(key)) {
509 continue;
510 }
janakre54491e2018-07-11 16:29:13 -0700511 Preconditions.checkState(
512 key.functionName().getHermeticity() == FunctionHermeticity.NONHERMETIC,
513 "Only non-hermetic keys can be dirty roots: %s",
514 key);
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000515 executor.execute(
516 wrapper.wrap(
fellyb6f86422018-02-20 19:01:35 -0800517 () -> {
518 SkyValue value;
519 try {
520 value = fetcher.get(key);
521 } catch (InterruptedException e) {
522 // Exit fast. Interrupt is handled below on the main thread.
523 return;
524 }
525 if (!checkMissingValues && value == null) {
526 return;
527 }
528
529 numKeysChecked.incrementAndGet();
530 DirtyResult result = checker.check(key, value, tsgm);
531 if (result.isDirty()) {
532 batchResult.add(key, value, result.getNewValue());
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000533 }
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000534 }));
535 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100536
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000537 boolean interrupted = ExecutorUtil.interruptibleShutdown(executor);
538 Throwables.propagateIfPossible(wrapper.getFirstThrownError());
539 if (interrupted) {
540 throw new InterruptedException();
541 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100542 }
543 return batchResult;
544 }
545
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100546 /**
Nathan Harmata8cd29782015-11-10 03:24:01 +0000547 * Result of a batch call to {@link SkyValueDirtinessChecker#check}. Partitions the dirty
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000548 * values based on whether we have a new value available for them or not.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100549 */
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000550 private static class BatchDirtyResult implements Differencer.DiffWithDelta {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100551
552 private final Set<SkyKey> concurrentDirtyKeysWithoutNewValues =
553 Collections.newSetFromMap(new ConcurrentHashMap<SkyKey, Boolean>());
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000554 private final ConcurrentHashMap<SkyKey, Delta> concurrentDirtyKeysWithNewAndOldValues =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100555 new ConcurrentHashMap<>();
556
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000557 private void add(SkyKey key, @Nullable SkyValue oldValue, @Nullable SkyValue newValue) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100558 if (newValue == null) {
559 concurrentDirtyKeysWithoutNewValues.add(key);
560 } else {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000561 if (oldValue == null) {
562 concurrentDirtyKeysWithNewAndOldValues.put(key, new Delta(newValue));
563 } else {
564 concurrentDirtyKeysWithNewAndOldValues.put(key, new Delta(oldValue, newValue));
565 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100566 }
567 }
568
569 @Override
Michajlo Matijkiw2b71efe2015-06-19 19:23:16 +0000570 public Collection<SkyKey> changedKeysWithoutNewValues() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100571 return concurrentDirtyKeysWithoutNewValues;
572 }
573
574 @Override
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000575 public Map<SkyKey, Delta> changedKeysWithNewAndOldValues() {
576 return concurrentDirtyKeysWithNewAndOldValues;
577 }
578
579 @Override
580 public Map<SkyKey, SkyValue> changedKeysWithNewValues() {
581 return Delta.newValues(concurrentDirtyKeysWithNewAndOldValues);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100582 }
583 }
584
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100585}