blob: e1d16bb9ecaf9a854a836d26c4f5be37edbc133a [file] [log] [blame]
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
2//
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.skyframe;
15
16import com.google.common.base.MoreObjects;
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +000018import com.google.common.collect.ImmutableList;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Iterables;
21import com.google.common.collect.Lists;
philwo3bcb9f62017-09-06 12:52:21 +020022import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet;
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +000023import com.google.devtools.build.skyframe.KeyToConsolidate.Op;
24import com.google.devtools.build.skyframe.KeyToConsolidate.OpToStoreBare;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.HashSet;
28import java.util.List;
29import java.util.Set;
30
31/**
32 * A utility class that allows us to keep the reverse dependencies as an array list instead of a
33 * set. This is more memory-efficient. At the same time it allows us to group the removals and
34 * uniqueness checks so that it also performs well.
35 *
36 * <p>We could simply make {@link InMemoryNodeEntry} extend this class, but we would be less
37 * memory-efficient since object memory alignment does not cross classes (you would have two memory
38 * alignments, one for the base class and one for the extended one). We could also merge this
39 * functionality directly into {@link InMemoryNodeEntry} at the cost of increasing its size and
40 * complexity even more.
41 *
42 * <p>The operations {@link #addReverseDeps}, {@link #checkReverseDep}, and {@link
43 * #removeReverseDep} here are optimized for a done entry. Done entries rarely have rdeps added and
44 * removed, but do have {@link Op#CHECK} operations performed frequently. As well, done node entries
45 * may never have their data forcibly consolidated, since their reverse deps will only be retrieved
46 * as a whole if they are marked dirty. Thus, we consolidate periodically.
47 *
48 * <p>{@link InMemoryNodeEntry} manages pending reverse dep operations on a marked-dirty or initally
49 * evaluating node itself, using similar logic tuned to those cases, and calls into {@link
50 * #consolidateDataAndReturnNewElements(InMemoryNodeEntry, OpToStoreBare)} when transitioning to
51 * done.
52 */
53abstract class ReverseDepsUtility {
54 private ReverseDepsUtility() {}
55
56 static final int MAYBE_CHECK_THRESHOLD = 10;
57
58 /**
59 * We can store one type of operation bare in order to save memory. For done nodes, most
60 * operations are CHECKS.
61 */
62 private static final OpToStoreBare DEFAULT_OP_TO_STORE_BARE = OpToStoreBare.CHECK;
63
64 private static void maybeDelayReverseDepOp(
65 InMemoryNodeEntry entry, Iterable<SkyKey> reverseDeps, Op op) {
66 List<Object> consolidations = entry.getReverseDepsDataToConsolidateForReverseDepsUtil();
67 int currentReverseDepSize = getCurrentReverseDepSize(entry);
68 if (consolidations == null) {
69 consolidations = new ArrayList<>(currentReverseDepSize);
70 entry.setReverseDepsDataToConsolidateForReverseDepsUtil(consolidations);
71 }
72 for (SkyKey reverseDep : reverseDeps) {
73 consolidations.add(KeyToConsolidate.create(reverseDep, op, DEFAULT_OP_TO_STORE_BARE));
74 }
75 // TODO(janakr): Should we consolidate more aggressively? This threshold can be customized.
76 if (consolidations.size() >= currentReverseDepSize) {
77 consolidateData(entry);
78 }
79 }
80
81 private static boolean isSingleReverseDep(InMemoryNodeEntry entry) {
82 return !(entry.getReverseDepsRawForReverseDepsUtil() instanceof List);
83 }
84
85 /**
86 * We only check if reverse deps is small and there are no delayed data to consolidate, since then
87 * presence or absence would not be known.
88 */
89 static void maybeCheckReverseDepNotPresent(InMemoryNodeEntry entry, SkyKey reverseDep) {
90 if (entry.getReverseDepsDataToConsolidateForReverseDepsUtil() != null) {
91 return;
92 }
93 if (isSingleReverseDep(entry)) {
94 Preconditions.checkState(
95 !entry.getReverseDepsRawForReverseDepsUtil().equals(reverseDep),
96 "Reverse dep %s already present in %s",
97 reverseDep,
98 entry);
99 return;
100 }
101 @SuppressWarnings("unchecked")
102 List<SkyKey> asList = (List<SkyKey>) entry.getReverseDepsRawForReverseDepsUtil();
103 if (asList.size() < MAYBE_CHECK_THRESHOLD) {
104 Preconditions.checkState(
105 !asList.contains(reverseDep),
106 "Reverse dep %s already present in %s for %s",
107 reverseDep,
108 asList,
109 entry);
110 }
111 }
112
113 @SuppressWarnings("unchecked") // Cast to list.
114 private static int getCurrentReverseDepSize(InMemoryNodeEntry entry) {
115 return isSingleReverseDep(entry)
116 ? 1
117 : ((List<SkyKey>) entry.getReverseDepsRawForReverseDepsUtil()).size();
118 }
119
120 /**
121 * We use a memory-efficient trick to keep reverseDeps memory usage low. Edges in Bazel are
122 * dominant over the number of nodes.
123 *
124 * <p>Most of the nodes have zero or one reverse dep. That is why we use immutable versions of the
125 * lists for those cases. In case of the size being > 1 we switch to an ArrayList. That is because
126 * we also have a decent number of nodes for which the reverseDeps are huge (for example almost
127 * everything depends on BuildInfo node).
128 *
129 * <p>We also optimize for the case where we have only one dependency. In that case we keep the
130 * object directly instead of a wrapper list.
131 */
132 @SuppressWarnings("unchecked") // Cast to SkyKey and List.
133 static void addReverseDeps(InMemoryNodeEntry entry, Collection<SkyKey> newReverseDeps) {
134 if (newReverseDeps.isEmpty()) {
135 return;
136 }
137 List<Object> dataToConsolidate = entry.getReverseDepsDataToConsolidateForReverseDepsUtil();
138 if (dataToConsolidate != null) {
139 maybeDelayReverseDepOp(entry, newReverseDeps, Op.ADD);
140 return;
141 }
142 Object reverseDeps = entry.getReverseDepsRawForReverseDepsUtil();
143 int reverseDepsSize = isSingleReverseDep(entry) ? 1 : ((List<SkyKey>) reverseDeps).size();
144 int newSize = reverseDepsSize + newReverseDeps.size();
145 if (newSize == 1) {
146 entry.setSingleReverseDepForReverseDepsUtil(Iterables.getOnlyElement(newReverseDeps));
147 } else if (reverseDepsSize == 0) {
148 entry.setReverseDepsForReverseDepsUtil(Lists.newArrayList(newReverseDeps));
149 } else if (reverseDepsSize == 1) {
150 List<SkyKey> newList = Lists.newArrayListWithExpectedSize(newSize);
151 newList.add((SkyKey) reverseDeps);
152 newList.addAll(newReverseDeps);
153 entry.setReverseDepsForReverseDepsUtil(newList);
154 } else {
155 ((List<SkyKey>) reverseDeps).addAll(newReverseDeps);
156 }
157 }
158
159 static void checkReverseDep(InMemoryNodeEntry entry, SkyKey reverseDep) {
160 maybeDelayReverseDepOp(entry, ImmutableList.of(reverseDep), Op.CHECK);
161 }
162
163 /** See {@code addReverseDeps} method. */
164 static void removeReverseDep(InMemoryNodeEntry entry, SkyKey reverseDep) {
165 maybeDelayReverseDepOp(entry, ImmutableList.of(reverseDep), Op.REMOVE);
166 }
167
168 static ImmutableSet<SkyKey> getReverseDeps(InMemoryNodeEntry entry) {
169 consolidateData(entry);
170
171 // TODO(bazel-team): Unfortunately, we need to make a copy here right now to be on the safe side
172 // wrt. thread-safety. The parents of a node get modified when any of the parents is deleted,
173 // and we can't handle that right now.
174 if (isSingleReverseDep(entry)) {
175 return ImmutableSet.of((SkyKey) entry.getReverseDepsRawForReverseDepsUtil());
176 } else {
177 @SuppressWarnings("unchecked")
178 List<SkyKey> reverseDeps = (List<SkyKey>) entry.getReverseDepsRawForReverseDepsUtil();
179 ImmutableSet<SkyKey> set = ImmutableSet.copyOf(reverseDeps);
180 Preconditions.checkState(
181 set.size() == reverseDeps.size(),
182 "Duplicate reverse deps present in %s: %s",
183 reverseDeps,
184 entry);
185 return set;
186 }
187 }
188
189 static Set<SkyKey> returnNewElements(InMemoryNodeEntry entry, OpToStoreBare opToStoreBare) {
190 return consolidateDataAndReturnNewElements(entry, false, opToStoreBare);
191 }
192
193 @SuppressWarnings("unchecked") // List and bare SkyKey casts.
194 private static Set<SkyKey> consolidateDataAndReturnNewElements(
195 InMemoryNodeEntry entry, boolean mutateObject, OpToStoreBare opToStoreBare) {
196 List<Object> dataToConsolidate = entry.getReverseDepsDataToConsolidateForReverseDepsUtil();
197 if (dataToConsolidate == null) {
198 return ImmutableSet.of();
199 }
200 Set<SkyKey> reverseDepsAsSet;
201 Object reverseDeps = entry.getReverseDepsRawForReverseDepsUtil();
202 if (isSingleReverseDep(entry)) {
203 reverseDepsAsSet = CompactHashSet.create((SkyKey) reverseDeps);
204 } else {
205 List<SkyKey> reverseDepsAsList = (List<SkyKey>) reverseDeps;
206 reverseDepsAsSet = getReverseDepsSet(entry, reverseDepsAsList);
207 }
208 Set<SkyKey> newData = CompactHashSet.create();
209 for (Object keyToConsolidate : dataToConsolidate) {
210 SkyKey key = KeyToConsolidate.key(keyToConsolidate);
211 switch (KeyToConsolidate.op(keyToConsolidate, opToStoreBare)) {
212 case CHECK:
213 Preconditions.checkState(
214 reverseDepsAsSet.contains(key),
tomlu32d2edc2017-11-08 18:14:23 +0100215 "Reverse dep not present: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000216 keyToConsolidate,
217 reverseDepsAsSet,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000218 dataToConsolidate,
219 entry);
220 Preconditions.checkState(
221 newData.add(key),
tomlu32d2edc2017-11-08 18:14:23 +0100222 "Duplicate new reverse dep: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000223 keyToConsolidate,
224 reverseDepsAsSet,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000225 dataToConsolidate,
226 entry);
227 break;
228 case REMOVE:
229 Preconditions.checkState(
230 reverseDepsAsSet.remove(key),
tomlu32d2edc2017-11-08 18:14:23 +0100231 "Reverse dep to be removed not present: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000232 keyToConsolidate,
233 reverseDepsAsSet,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000234 dataToConsolidate,
235 entry);
236 Preconditions.checkState(
237 newData.remove(key),
tomlu32d2edc2017-11-08 18:14:23 +0100238 "Reverse dep to be removed not present: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000239 keyToConsolidate,
240 reverseDepsAsSet,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000241 dataToConsolidate,
242 entry);
243 break;
244 case REMOVE_OLD:
245 Preconditions.checkState(
246 reverseDepsAsSet.remove(key),
tomlu32d2edc2017-11-08 18:14:23 +0100247 "Reverse dep to be removed not present: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000248 keyToConsolidate,
249 reverseDepsAsSet,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000250 dataToConsolidate,
251 entry);
252 Preconditions.checkState(
253 !newData.contains(key),
tomlu32d2edc2017-11-08 18:14:23 +0100254 "Reverse dep shouldn't have been added to new: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000255 keyToConsolidate,
256 reverseDepsAsSet,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000257 dataToConsolidate,
258 entry);
259 break;
260 case ADD:
261 Preconditions.checkState(
262 reverseDepsAsSet.add(key),
tomlu32d2edc2017-11-08 18:14:23 +0100263 "Duplicate reverse deps: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000264 keyToConsolidate,
265 reverseDeps,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000266 dataToConsolidate,
267 entry);
268 Preconditions.checkState(
269 newData.add(key),
tomlu32d2edc2017-11-08 18:14:23 +0100270 "Duplicate new reverse deps: %s %s %s %s",
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000271 keyToConsolidate,
272 reverseDeps,
Janak Ramakrishnancb8a97d2017-03-23 20:50:08 +0000273 dataToConsolidate,
274 entry);
275 break;
276 default:
277 throw new IllegalStateException(
278 keyToConsolidate + ", " + reverseDepsAsSet + ", " + dataToConsolidate + ", " + entry);
279 }
280 }
281 if (mutateObject) {
282 entry.setReverseDepsDataToConsolidateForReverseDepsUtil(null);
283 writeReverseDepsSet(entry, reverseDepsAsSet);
284 }
285 return newData;
286 }
287
288 static Set<SkyKey> consolidateDataAndReturnNewElements(
289 InMemoryNodeEntry entry, OpToStoreBare opToStoreBare) {
290 return consolidateDataAndReturnNewElements(entry, true, opToStoreBare);
291 }
292
293 @SuppressWarnings("unchecked") // Casts to SkyKey and List.
294 private static void consolidateData(InMemoryNodeEntry entry) {
295 List<Object> dataToConsolidate = entry.getReverseDepsDataToConsolidateForReverseDepsUtil();
296 if (dataToConsolidate == null) {
297 return;
298 }
299 entry.setReverseDepsDataToConsolidateForReverseDepsUtil(null);
300 Object reverseDeps = entry.getReverseDepsRawForReverseDepsUtil();
301 if (isSingleReverseDep(entry)) {
302 Preconditions.checkState(
303 dataToConsolidate.size() == 1,
304 "dataToConsolidate not size 1 even though only one rdep: %s %s %s",
305 dataToConsolidate,
306 reverseDeps,
307 entry);
308 Object keyToConsolidate = Iterables.getOnlyElement(dataToConsolidate);
309 SkyKey key = KeyToConsolidate.key(keyToConsolidate);
310 switch (KeyToConsolidate.op(keyToConsolidate, DEFAULT_OP_TO_STORE_BARE)) {
311 case REMOVE:
312 entry.setReverseDepsForReverseDepsUtil(ImmutableList.<SkyKey>of());
313 // Fall through to check.
314 case CHECK:
315 Preconditions.checkState(
316 key.equals(reverseDeps), "%s %s %s", keyToConsolidate, reverseDeps, entry);
317 break;
318 case ADD:
319 throw new IllegalStateException(
320 "Shouldn't delay add if only one element: "
321 + keyToConsolidate
322 + ", "
323 + reverseDeps
324 + ", "
325 + entry);
326 case REMOVE_OLD:
327 throw new IllegalStateException(
328 "Shouldn't be removing old deps if node already done: "
329 + keyToConsolidate
330 + ", "
331 + reverseDeps
332 + ", "
333 + entry);
334 default:
335 throw new IllegalStateException(keyToConsolidate + ", " + reverseDeps + ", " + entry);
336 }
337 return;
338 }
339 List<SkyKey> reverseDepsAsList = (List<SkyKey>) reverseDeps;
340 Set<SkyKey> reverseDepsAsSet = getReverseDepsSet(entry, reverseDepsAsList);
341
342 for (Object keyToConsolidate : dataToConsolidate) {
343 SkyKey key = KeyToConsolidate.key(keyToConsolidate);
344 switch (KeyToConsolidate.op(keyToConsolidate, DEFAULT_OP_TO_STORE_BARE)) {
345 case CHECK:
346 Preconditions.checkState(
347 reverseDepsAsSet.contains(key),
348 "%s %s %s %s",
349 keyToConsolidate,
350 reverseDepsAsSet,
351 dataToConsolidate,
352 entry);
353 break;
354 case REMOVE:
355 Preconditions.checkState(
356 reverseDepsAsSet.remove(key),
357 "%s %s %s %s",
358 keyToConsolidate,
359 reverseDeps,
360 dataToConsolidate,
361 entry);
362 break;
363 case ADD:
364 Preconditions.checkState(
365 reverseDepsAsSet.add(key),
366 "%s %s %s %s",
367 keyToConsolidate,
368 reverseDeps,
369 dataToConsolidate,
370 entry);
371 break;
372 case REMOVE_OLD:
373 throw new IllegalStateException(
374 "Shouldn't be removing old deps if node already done: "
375 + keyToConsolidate
376 + ", "
377 + reverseDeps
378 + ", "
379 + dataToConsolidate
380 + ", "
381 + entry);
382 default:
383 throw new IllegalStateException(
384 keyToConsolidate + ", " + reverseDepsAsSet + ", " + dataToConsolidate + ", " + entry);
385 }
386 }
387 writeReverseDepsSet(entry, reverseDepsAsSet);
388 }
389
390 private static void writeReverseDepsSet(InMemoryNodeEntry entry, Set<SkyKey> reverseDepsAsSet) {
391 if (reverseDepsAsSet.isEmpty()) {
392 entry.setReverseDepsForReverseDepsUtil(ImmutableList.<SkyKey>of());
393 } else if (reverseDepsAsSet.size() == 1) {
394 entry.setSingleReverseDepForReverseDepsUtil(Iterables.getOnlyElement(reverseDepsAsSet));
395 } else {
396 entry.setReverseDepsForReverseDepsUtil(new ArrayList<>(reverseDepsAsSet));
397 }
398 }
399
400 private static Set<SkyKey> getReverseDepsSet(
401 InMemoryNodeEntry entry, List<SkyKey> reverseDepsAsList) {
402 Set<SkyKey> reverseDepsAsSet = CompactHashSet.create(reverseDepsAsList);
403
404 if (reverseDepsAsSet.size() != reverseDepsAsList.size()) {
405 // We're about to crash. Try to print an informative error message.
406 Set<SkyKey> seen = new HashSet<>();
407 List<SkyKey> duplicates = new ArrayList<>();
408 for (SkyKey key : reverseDepsAsList) {
409 if (!seen.add(key)) {
410 duplicates.add(key);
411 }
412 }
413 throw new IllegalStateException(
414 (reverseDepsAsList.size() - reverseDepsAsSet.size())
415 + " duplicates: "
416 + duplicates
417 + " for "
418 + entry);
419 }
420 return reverseDepsAsSet;
421 }
422
423 static String toString(InMemoryNodeEntry entry) {
424 return MoreObjects.toStringHelper("ReverseDeps")
425 .add("reverseDeps", entry.getReverseDepsRawForReverseDepsUtil())
426 .add("singleReverseDep", isSingleReverseDep(entry))
427 .add("dataToConsolidate", entry.getReverseDepsDataToConsolidateForReverseDepsUtil())
428 .toString();
429 }
430}