blob: c99898028a25268ab6aa1866c368fc5b9e729837 [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;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046import com.google.devtools.build.skyframe.SkyFunctionName;
47import com.google.devtools.build.skyframe.SkyKey;
48import com.google.devtools.build.skyframe.SkyValue;
Nathan Harmata8cd29782015-11-10 03:24:01 +000049import com.google.devtools.build.skyframe.WalkableGraph;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010050import java.io.IOException;
51import java.util.Collection;
52import java.util.Collections;
53import java.util.HashMap;
Nathan Harmata8cd29782015-11-10 03:24:01 +000054import java.util.HashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010055import java.util.List;
56import java.util.Map;
Rumou Duan45e8e572016-06-17 16:43:44 +000057import java.util.NavigableSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010058import java.util.Set;
59import java.util.concurrent.ConcurrentHashMap;
60import java.util.concurrent.ExecutorService;
61import java.util.concurrent.Executors;
cushon4d70fae2017-04-11 01:01:13 +000062import java.util.concurrent.Future;
Nathan Harmatae1b61d02015-10-06 00:09:42 +000063import java.util.concurrent.TimeUnit;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010064import java.util.concurrent.atomic.AtomicInteger;
65import java.util.logging.Level;
66import java.util.logging.Logger;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010067import javax.annotation.Nullable;
68
69/**
70 * A helper class to find dirty values by accessing the filesystem directly (contrast with
71 * {@link DiffAwareness}).
72 */
Nathan Harmata8cd29782015-11-10 03:24:01 +000073public class FilesystemValueChecker {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010074
Nathan Harmata3a509bd2015-10-06 01:00:47 +000075 private static final int DIRTINESS_CHECK_THREADS = 200;
lberki97abb522017-09-04 18:51:57 +020076 private static final Logger logger = Logger.getLogger(FilesystemValueChecker.class.getName());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010077
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010078 private static final Predicate<SkyKey> ACTION_FILTER =
79 SkyFunctionName.functionIs(SkyFunctions.ACTION_EXECUTION);
80
81 private final TimestampGranularityMonitor tsgm;
Nathan Harmata9b38b2c2015-08-27 16:11:07 +000082 @Nullable
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000083 private final Range<Long> lastExecutionTimeRange;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010084 private AtomicInteger modifiedOutputFilesCounter = new AtomicInteger(0);
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +000085 private AtomicInteger modifiedOutputFilesIntraBuildCounter = new AtomicInteger(0);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010086
Nathan Harmata8cd29782015-11-10 03:24:01 +000087 public FilesystemValueChecker(@Nullable TimestampGranularityMonitor tsgm,
88 @Nullable Range<Long> lastExecutionTimeRange) {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +000089 this.tsgm = tsgm;
90 this.lastExecutionTimeRange = lastExecutionTimeRange;
91 }
92
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010093 /**
Nathan Harmata8cd29782015-11-10 03:24:01 +000094 * Returns a {@link Differencer.DiffWithDelta} containing keys from the give map that are dirty
95 * according to the passed-in {@code dirtinessChecker}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010096 */
Nathan Harmata8cd29782015-11-10 03:24:01 +000097 // TODO(bazel-team): Refactor these methods so that FilesystemValueChecker only operates on a
98 // WalkableGraph.
99 Differencer.DiffWithDelta getDirtyKeys(Map<SkyKey, SkyValue> valuesMap,
100 SkyValueDirtinessChecker dirtinessChecker) throws InterruptedException {
101 return getDirtyValues(new MapBackedValueFetcher(valuesMap), valuesMap.keySet(),
102 dirtinessChecker, /*checkMissingValues=*/false);
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000103 }
104
105 /**
106 * Returns a {@link Differencer.DiffWithDelta} containing keys that are dirty according to the
107 * passed-in {@code dirtinessChecker}.
108 */
Nathan Harmata8cd29782015-11-10 03:24:01 +0000109 public Differencer.DiffWithDelta getNewAndOldValues(Map<SkyKey, SkyValue> valuesMap,
110 Iterable<SkyKey> keys, SkyValueDirtinessChecker dirtinessChecker)
111 throws InterruptedException {
112 return getDirtyValues(new MapBackedValueFetcher(valuesMap), keys,
113 dirtinessChecker, /*checkMissingValues=*/true);
114 }
115
116 /**
117 * Returns a {@link Differencer.DiffWithDelta} containing keys that are dirty according to the
118 * passed-in {@code dirtinessChecker}.
119 */
120 public Differencer.DiffWithDelta getNewAndOldValues(WalkableGraph walkableGraph,
121 Iterable<SkyKey> keys, SkyValueDirtinessChecker dirtinessChecker)
122 throws InterruptedException {
123 return getDirtyValues(new WalkableGraphBackedValueFetcher(walkableGraph), keys,
124 dirtinessChecker, /*checkMissingValues=*/true);
125 }
126
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000127 private interface ValueFetcher {
Nathan Harmata8cd29782015-11-10 03:24:01 +0000128 @Nullable
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000129 SkyValue get(SkyKey key) throws InterruptedException;
Nathan Harmata8cd29782015-11-10 03:24:01 +0000130 }
131
132 private static class WalkableGraphBackedValueFetcher implements ValueFetcher {
133 private final WalkableGraph walkableGraph;
134
135 private WalkableGraphBackedValueFetcher(WalkableGraph walkableGraph) {
136 this.walkableGraph = walkableGraph;
137 }
138
139 @Override
140 @Nullable
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000141 public SkyValue get(SkyKey key) throws InterruptedException {
Janak Ramakrishnan112840b2016-12-29 21:49:56 +0000142 return walkableGraph.getValue(key);
Nathan Harmata8cd29782015-11-10 03:24:01 +0000143 }
144 }
145
146 private static class MapBackedValueFetcher implements ValueFetcher {
147 private final Map<SkyKey, SkyValue> valuesMap;
148
149 private MapBackedValueFetcher(Map<SkyKey, SkyValue> valuesMap) {
150 this.valuesMap = valuesMap;
151 }
152
153 @Override
154 @Nullable
155 public SkyValue get(SkyKey key) {
156 return valuesMap.get(key);
157 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100158 }
159
160 /**
161 * Return a collection of action values which have output files that are not in-sync with
162 * the on-disk file value (were modified externally).
163 */
Nathan Harmata8cd29782015-11-10 03:24:01 +0000164 Collection<SkyKey> getDirtyActionValues(Map<SkyKey, SkyValue> valuesMap,
Eric Fellheimere6590722015-11-17 17:07:48 +0000165 @Nullable final BatchStat batchStatter, ModifiedFileSet modifiedOutputFiles)
166 throws InterruptedException {
167 if (modifiedOutputFiles == ModifiedFileSet.NOTHING_MODIFIED) {
lberki97abb522017-09-04 18:51:57 +0200168 logger.info("Not checking for dirty actions since nothing was modified");
Eric Fellheimere6590722015-11-17 17:07:48 +0000169 return ImmutableList.of();
170 }
lberki97abb522017-09-04 18:51:57 +0200171 logger.info("Accumulating dirty actions");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100172 final int numOutputJobs = Runtime.getRuntime().availableProcessors() * 4;
Nathan Harmata8cd29782015-11-10 03:24:01 +0000173 final Set<SkyKey> actionSkyKeys = new HashSet<>();
twerth646dfd12018-07-04 01:58:40 -0700174 try (SilentCloseable c = Profiler.instance().profile("getDirtyActionValues.filter_actions")) {
175 for (SkyKey key : valuesMap.keySet()) {
176 if (ACTION_FILTER.apply(key)) {
177 actionSkyKeys.add(key);
178 }
Nathan Harmata8cd29782015-11-10 03:24:01 +0000179 }
180 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100181 final Sharder<Pair<SkyKey, ActionExecutionValue>> outputShards =
182 new Sharder<>(numOutputJobs, actionSkyKeys.size());
183
184 for (SkyKey key : actionSkyKeys) {
Nathan Harmata8cd29782015-11-10 03:24:01 +0000185 outputShards.add(Pair.of(key, (ActionExecutionValue) valuesMap.get(key)));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100186 }
lberki97abb522017-09-04 18:51:57 +0200187 logger.info("Sharded action values for batching");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188
189 ExecutorService executor = Executors.newFixedThreadPool(
190 numOutputJobs,
191 new ThreadFactoryBuilder().setNameFormat("FileSystem Output File Invalidator %d").build());
192
193 Collection<SkyKey> dirtyKeys = Sets.newConcurrentHashSet();
194 ThrowableRecordingRunnableWrapper wrapper =
195 new ThrowableRecordingRunnableWrapper("FileSystemValueChecker#getDirtyActionValues");
196
197 modifiedOutputFilesCounter.set(0);
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000198 modifiedOutputFilesIntraBuildCounter.set(0);
Rumou Duan45e8e572016-06-17 16:43:44 +0000199 final ImmutableSet<PathFragment> knownModifiedOutputFiles =
Eric Fellheimere6590722015-11-17 17:07:48 +0000200 modifiedOutputFiles == ModifiedFileSet.EVERYTHING_MODIFIED
201 ? null
202 : modifiedOutputFiles.modifiedSourceFiles();
Rumou Duan45e8e572016-06-17 16:43:44 +0000203
204 // Initialized lazily through a supplier because it is only used to check modified
205 // TreeArtifacts, which are not frequently used in builds.
206 Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles =
207 Suppliers.memoize(new Supplier<NavigableSet<PathFragment>>() {
208 @Override
209 public NavigableSet<PathFragment> get() {
210 if (knownModifiedOutputFiles == null) {
211 return null;
212 } else {
213 return ImmutableSortedSet.copyOf(knownModifiedOutputFiles);
214 }
215 }
216 });
217
twerth646dfd12018-07-04 01:58:40 -0700218 boolean interrupted;
219 try (SilentCloseable c = Profiler.instance().profile("getDirtyActionValues.stat_files")) {
220 for (List<Pair<SkyKey, ActionExecutionValue>> shard : outputShards) {
221 Runnable job =
222 (batchStatter == null)
223 ? outputStatJob(
224 dirtyKeys, shard, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)
225 : batchStatJob(
226 dirtyKeys,
227 shard,
228 batchStatter,
229 knownModifiedOutputFiles,
230 sortedKnownModifiedOutputFiles);
231 Future<?> unused = executor.submit(wrapper.wrap(job));
232 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100233
twerth646dfd12018-07-04 01:58:40 -0700234 interrupted = ExecutorUtil.interruptibleShutdown(executor);
235 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100236 Throwables.propagateIfPossible(wrapper.getFirstThrownError());
lberki97abb522017-09-04 18:51:57 +0200237 logger.info("Completed output file stat checks");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100238 if (interrupted) {
239 throw new InterruptedException();
240 }
241 return dirtyKeys;
242 }
243
244 private Runnable batchStatJob(final Collection<SkyKey> dirtyKeys,
Eric Fellheimere6590722015-11-17 17:07:48 +0000245 final List<Pair<SkyKey, ActionExecutionValue>> shard,
Rumou Duan45e8e572016-06-17 16:43:44 +0000246 final BatchStat batchStatter, final ImmutableSet<PathFragment> knownModifiedOutputFiles,
247 final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100248 return new Runnable() {
249 @Override
250 public void run() {
Rumou Duana77f32c2016-04-13 21:59:21 +0000251 Map<Artifact, Pair<SkyKey, ActionExecutionValue>> fileToKeyAndValue = new HashMap<>();
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000252 Map<Artifact, Pair<SkyKey, ActionExecutionValue>> treeArtifactsToKeyAndValue =
253 new HashMap<>();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100254 for (Pair<SkyKey, ActionExecutionValue> keyAndValue : shard) {
255 ActionExecutionValue actionValue = keyAndValue.getSecond();
256 if (actionValue == null) {
257 dirtyKeys.add(keyAndValue.getFirst());
258 } else {
Rumou Duana77f32c2016-04-13 21:59:21 +0000259 for (Artifact artifact : actionValue.getAllFileValues().keySet()) {
260 if (shouldCheckFile(knownModifiedOutputFiles, artifact)) {
261 fileToKeyAndValue.put(artifact, keyAndValue);
Eric Fellheimere6590722015-11-17 17:07:48 +0000262 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100263 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000264
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000265 for (Artifact artifact : actionValue.getAllTreeArtifactValues().keySet()) {
Rumou Duan45e8e572016-06-17 16:43:44 +0000266 if (shouldCheckTreeArtifact(sortedKnownModifiedOutputFiles.get(), artifact)) {
267 treeArtifactsToKeyAndValue.put(artifact, keyAndValue);
268 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000269 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100270 }
271 }
272
Rumou Duana77f32c2016-04-13 21:59:21 +0000273 List<Artifact> artifacts = ImmutableList.copyOf(fileToKeyAndValue.keySet());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100274 List<FileStatusWithDigest> stats;
275 try {
lberki97abb522017-09-04 18:51:57 +0200276 stats =
277 batchStatter.batchStat(
278 /*includeDigest=*/ true,
279 /*includeLinks=*/ true,
280 Artifact.asPathFragments(artifacts));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100281 } catch (IOException e) {
282 // Batch stat did not work. Log an exception and fall back on system calls.
283 LoggingUtil.logToRemote(Level.WARNING, "Unable to process batch stat", e);
lberki97abb522017-09-04 18:51:57 +0200284 logger.log(Level.WARNING, "Unable to process batch stat", e);
Rumou Duan45e8e572016-06-17 16:43:44 +0000285 outputStatJob(dirtyKeys, shard, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)
286 .run();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 return;
288 } catch (InterruptedException e) {
289 // We handle interrupt in the main thread.
290 return;
291 }
292
lberki97abb522017-09-04 18:51:57 +0200293 Preconditions.checkState(
294 artifacts.size() == stats.size(),
295 "artifacts.size() == %s stats.size() == %s",
296 artifacts.size(),
297 stats.size());
Rumou Duana77f32c2016-04-13 21:59:21 +0000298 for (int i = 0; i < artifacts.size(); i++) {
299 Artifact artifact = artifacts.get(i);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100300 FileStatusWithDigest stat = stats.get(i);
Rumou Duana77f32c2016-04-13 21:59:21 +0000301 Pair<SkyKey, ActionExecutionValue> keyAndValue = fileToKeyAndValue.get(artifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100302 ActionExecutionValue actionValue = keyAndValue.getSecond();
303 SkyKey key = keyAndValue.getFirst();
Rumou Duana77f32c2016-04-13 21:59:21 +0000304 FileValue lastKnownData = actionValue.getAllFileValues().get(artifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100305 try {
lberki97abb522017-09-04 18:51:57 +0200306 FileValue newData = ActionMetadataHandler.fileValueFromArtifact(artifact, stat, tsgm);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100307 if (!newData.equals(lastKnownData)) {
lberki97abb522017-09-04 18:51:57 +0200308 updateIntraBuildModifiedCounter(
309 stat != null ? stat.getLastChangeTime() : -1,
310 lastKnownData.isSymlink(),
311 newData.isSymlink());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100312 modifiedOutputFilesCounter.getAndIncrement();
313 dirtyKeys.add(key);
314 }
315 } catch (IOException e) {
316 // This is an unexpected failure getting a digest or symlink target.
317 modifiedOutputFilesCounter.getAndIncrement();
318 dirtyKeys.add(key);
319 }
320 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000321
322 // Unfortunately, there exists no facility to batch list directories.
323 // We must use direct filesystem calls.
324 for (Map.Entry<Artifact, Pair<SkyKey, ActionExecutionValue>> entry :
325 treeArtifactsToKeyAndValue.entrySet()) {
326 Artifact artifact = entry.getKey();
327 if (treeArtifactIsDirty(
328 entry.getKey(), entry.getValue().getSecond().getTreeArtifactValue(artifact))) {
329 Path path = artifact.getPath();
330 // Count the changed directory as one "file".
331 // TODO(bazel-team): There are no tests for this codepath.
332 try {
lberki97abb522017-09-04 18:51:57 +0200333 updateIntraBuildModifiedCounter(
334 path.exists() ? path.getLastModifiedTime() : -1, false, path.isSymbolicLink());
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000335 } catch (IOException e) {
336 // Do nothing here.
337 }
338
339 modifiedOutputFilesCounter.getAndIncrement();
340 dirtyKeys.add(entry.getValue().getFirst());
341 }
342 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100343 }
344 };
345 }
346
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000347 private void updateIntraBuildModifiedCounter(long time, boolean oldWasSymlink,
Nathan Harmata7a344272015-06-05 20:52:38 +0000348 boolean newIsSymlink) {
Miguel Alcon Pintobdfdd092015-04-10 19:04:49 +0000349 if (lastExecutionTimeRange != null
350 && lastExecutionTimeRange.contains(time)
351 && !(oldWasSymlink && newIsSymlink)) {
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000352 modifiedOutputFilesIntraBuildCounter.incrementAndGet();
353 }
354 }
355
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100356 private Runnable outputStatJob(final Collection<SkyKey> dirtyKeys,
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000357 final List<Pair<SkyKey, ActionExecutionValue>> shard,
Rumou Duan45e8e572016-06-17 16:43:44 +0000358 final ImmutableSet<PathFragment> knownModifiedOutputFiles,
359 final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100360 return new Runnable() {
361 @Override
362 public void run() {
363 for (Pair<SkyKey, ActionExecutionValue> keyAndValue : shard) {
364 ActionExecutionValue value = keyAndValue.getSecond();
Eric Fellheimere6590722015-11-17 17:07:48 +0000365 if (value == null
Rumou Duan45e8e572016-06-17 16:43:44 +0000366 || actionValueIsDirtyWithDirectSystemCalls(
367 value, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100368 dirtyKeys.add(keyAndValue.getFirst());
369 }
370 }
371 }
372 };
373 }
374
375 /**
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000376 * Returns the number of modified output files inside of dirty actions.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100377 */
378 int getNumberOfModifiedOutputFiles() {
379 return modifiedOutputFilesCounter.get();
380 }
381
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000382 /** Returns the number of modified output files that occur during the previous build. */
383 int getNumberOfModifiedOutputFilesDuringPreviousBuild() {
Miguel Alcon Pinto7cf23652015-03-10 21:27:48 +0000384 return modifiedOutputFilesIntraBuildCounter.get();
385 }
386
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000387 private boolean treeArtifactIsDirty(Artifact artifact, TreeArtifactValue value) {
388 if (artifact.getPath().isSymbolicLink()) {
389 // TreeArtifacts may not be symbolic links.
390 return true;
391 }
392
393 // There doesn't appear to be any facility to batch list directories... we must
394 // do things the 'slow' way.
395 try {
396 Set<PathFragment> currentDirectoryValue = TreeArtifactValue.explodeDirectory(artifact);
397 Set<PathFragment> valuePaths = value.getChildPaths();
398 return !currentDirectoryValue.equals(valuePaths);
Rumou Duan9ad28cd2016-10-19 19:28:06 +0000399 } catch (IOException e) {
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000400 return true;
401 }
402 }
403
Eric Fellheimere6590722015-11-17 17:07:48 +0000404 private boolean actionValueIsDirtyWithDirectSystemCalls(ActionExecutionValue actionValue,
Rumou Duan45e8e572016-06-17 16:43:44 +0000405 ImmutableSet<PathFragment> knownModifiedOutputFiles,
406 Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100407 boolean isDirty = false;
Rumou Duana77f32c2016-04-13 21:59:21 +0000408 for (Map.Entry<Artifact, FileValue> entry : actionValue.getAllFileValues().entrySet()) {
409 Artifact file = entry.getKey();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100410 FileValue lastKnownData = entry.getValue();
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000411 if (shouldCheckFile(knownModifiedOutputFiles, file)) {
Eric Fellheimere6590722015-11-17 17:07:48 +0000412 try {
Rumou Duana77f32c2016-04-13 21:59:21 +0000413 FileValue fileValue = ActionMetadataHandler.fileValueFromArtifact(file, null,
414 tsgm);
Eric Fellheimere6590722015-11-17 17:07:48 +0000415 if (!fileValue.equals(lastKnownData)) {
416 updateIntraBuildModifiedCounter(fileValue.exists()
417 ? fileValue.realRootedPath().asPath().getLastModifiedTime()
418 : -1, lastKnownData.isSymlink(), fileValue.isSymlink());
419 modifiedOutputFilesCounter.getAndIncrement();
420 isDirty = true;
421 }
422 } catch (IOException e) {
423 // This is an unexpected failure getting a digest or symlink target.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100424 modifiedOutputFilesCounter.getAndIncrement();
425 isDirty = true;
426 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100427 }
428 }
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000429
430 for (Map.Entry<Artifact, TreeArtifactValue> entry :
431 actionValue.getAllTreeArtifactValues().entrySet()) {
432 Artifact artifact = entry.getKey();
Rumou Duan45e8e572016-06-17 16:43:44 +0000433
434 if (shouldCheckTreeArtifact(sortedKnownModifiedOutputFiles.get(), artifact)
435 && treeArtifactIsDirty(artifact, entry.getValue())) {
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000436 Path path = artifact.getPath();
437 // Count the changed directory as one "file".
438 try {
439 updateIntraBuildModifiedCounter(path.exists()
440 ? path.getLastModifiedTime()
441 : -1, false, path.isSymbolicLink());
442 } catch (IOException e) {
443 // Do nothing here.
444 }
445
446 modifiedOutputFilesCounter.getAndIncrement();
447 isDirty = true;
448 }
449 }
450
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100451 return isDirty;
452 }
453
Michael Thvedte4a7b0792016-02-09 12:15:53 +0000454 private static boolean shouldCheckFile(ImmutableSet<PathFragment> knownModifiedOutputFiles,
Rumou Duana77f32c2016-04-13 21:59:21 +0000455 Artifact artifact) {
Eric Fellheimere6590722015-11-17 17:07:48 +0000456 return knownModifiedOutputFiles == null
Rumou Duana77f32c2016-04-13 21:59:21 +0000457 || knownModifiedOutputFiles.contains(artifact.getExecPath());
Eric Fellheimere6590722015-11-17 17:07:48 +0000458 }
459
Rumou Duan45e8e572016-06-17 16:43:44 +0000460 private static boolean shouldCheckTreeArtifact(
461 @Nullable NavigableSet<PathFragment> knownModifiedOutputFiles, Artifact treeArtifact) {
462 // If null, everything needs to be checked.
463 if (knownModifiedOutputFiles == null) {
464 return true;
465 }
466
467 // Here we do the following to see whether a TreeArtifact is modified:
468 // 1. Sort the set of modified file paths in lexicographical order using TreeSet.
469 // 2. Get the first modified output file path that is greater than or equal to the exec path of
470 // the TreeArtifact to check.
471 // 3. Check whether the returned file path contains the exec path of the TreeArtifact as a
472 // prefix path.
473 PathFragment artifactExecPath = treeArtifact.getExecPath();
474 PathFragment headPath = knownModifiedOutputFiles.ceiling(artifactExecPath);
475
476 return headPath != null && headPath.startsWith(artifactExecPath);
477 }
478
Nathan Harmata8cd29782015-11-10 03:24:01 +0000479 private BatchDirtyResult getDirtyValues(ValueFetcher fetcher,
480 Iterable<SkyKey> keys, final SkyValueDirtinessChecker checker,
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000481 final boolean checkMissingValues) throws InterruptedException {
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000482 ExecutorService executor =
483 Executors.newFixedThreadPool(
484 DIRTINESS_CHECK_THREADS,
485 new ThreadFactoryBuilder().setNameFormat("FileSystem Value Invalidator %d").build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100486
487 final BatchDirtyResult batchResult = new BatchDirtyResult();
488 ThrowableRecordingRunnableWrapper wrapper =
489 new ThrowableRecordingRunnableWrapper("FilesystemValueChecker#getDirtyValues");
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000490 final AtomicInteger numKeysScanned = new AtomicInteger(0);
491 final AtomicInteger numKeysChecked = new AtomicInteger(0);
lberki97abb522017-09-04 18:51:57 +0200492 ElapsedTimeReceiver elapsedTimeReceiver =
fellyb6f86422018-02-20 19:01:35 -0800493 elapsedTimeNanos -> {
494 if (elapsedTimeNanos > 0) {
495 logger.info(
496 String.format(
497 "Spent %d ms checking %d filesystem nodes (%d scanned)",
498 TimeUnit.MILLISECONDS.convert(elapsedTimeNanos, TimeUnit.NANOSECONDS),
499 numKeysChecked.get(),
500 numKeysScanned.get()));
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000501 }
lberki97abb522017-09-04 18:51:57 +0200502 };
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000503 try (AutoProfiler prof = AutoProfiler.create(elapsedTimeReceiver)) {
Nathan Harmata8cd29782015-11-10 03:24:01 +0000504 for (final SkyKey key : keys) {
Nathan Harmata3a509bd2015-10-06 01:00:47 +0000505 numKeysScanned.incrementAndGet();
506 if (!checker.applies(key)) {
507 continue;
508 }
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000509 executor.execute(
510 wrapper.wrap(
fellyb6f86422018-02-20 19:01:35 -0800511 () -> {
512 SkyValue value;
513 try {
514 value = fetcher.get(key);
515 } catch (InterruptedException e) {
516 // Exit fast. Interrupt is handled below on the main thread.
517 return;
518 }
519 if (!checkMissingValues && value == null) {
520 return;
521 }
522
523 numKeysChecked.incrementAndGet();
524 DirtyResult result = checker.check(key, value, tsgm);
525 if (result.isDirty()) {
526 batchResult.add(key, value, result.getNewValue());
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000527 }
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000528 }));
529 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100530
Nathan Harmatae1b61d02015-10-06 00:09:42 +0000531 boolean interrupted = ExecutorUtil.interruptibleShutdown(executor);
532 Throwables.propagateIfPossible(wrapper.getFirstThrownError());
533 if (interrupted) {
534 throw new InterruptedException();
535 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100536 }
537 return batchResult;
538 }
539
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100540 /**
Nathan Harmata8cd29782015-11-10 03:24:01 +0000541 * Result of a batch call to {@link SkyValueDirtinessChecker#check}. Partitions the dirty
Janak Ramakrishnan6ddbb6e2015-07-28 21:39:22 +0000542 * values based on whether we have a new value available for them or not.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100543 */
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000544 private static class BatchDirtyResult implements Differencer.DiffWithDelta {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100545
546 private final Set<SkyKey> concurrentDirtyKeysWithoutNewValues =
547 Collections.newSetFromMap(new ConcurrentHashMap<SkyKey, Boolean>());
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000548 private final ConcurrentHashMap<SkyKey, Delta> concurrentDirtyKeysWithNewAndOldValues =
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100549 new ConcurrentHashMap<>();
550
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000551 private void add(SkyKey key, @Nullable SkyValue oldValue, @Nullable SkyValue newValue) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100552 if (newValue == null) {
553 concurrentDirtyKeysWithoutNewValues.add(key);
554 } else {
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000555 if (oldValue == null) {
556 concurrentDirtyKeysWithNewAndOldValues.put(key, new Delta(newValue));
557 } else {
558 concurrentDirtyKeysWithNewAndOldValues.put(key, new Delta(oldValue, newValue));
559 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100560 }
561 }
562
563 @Override
Michajlo Matijkiw2b71efe2015-06-19 19:23:16 +0000564 public Collection<SkyKey> changedKeysWithoutNewValues() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100565 return concurrentDirtyKeysWithoutNewValues;
566 }
567
568 @Override
Nathan Harmata2ff0a6d2015-08-12 21:10:56 +0000569 public Map<SkyKey, Delta> changedKeysWithNewAndOldValues() {
570 return concurrentDirtyKeysWithNewAndOldValues;
571 }
572
573 @Override
574 public Map<SkyKey, SkyValue> changedKeysWithNewValues() {
575 return Delta.newValues(concurrentDirtyKeysWithNewAndOldValues);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100576 }
577 }
578
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100579}