blob: d0f6d4ac82a8fc9cbe4d863b302af16b04270074 [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.skyframe;
15
Googler2c19a572015-07-16 08:38:49 +000016import com.google.common.base.MoreObjects;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017import com.google.common.base.Preconditions;
18import com.google.common.collect.ImmutableList;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Iterables;
21import com.google.common.collect.Lists;
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000022import com.google.devtools.build.lib.collect.CompactHashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000024import java.util.ArrayList;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import java.util.Collection;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000026import java.util.HashSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import java.util.List;
28import java.util.Set;
29
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000030import javax.annotation.Nullable;
31
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010032/**
33 * A utility class that allows us to keep the reverse dependencies as an array list instead of a
34 * set. This is more memory-efficient. At the same time it allows us to group the removals and
35 * uniqueness checks so that it also performs well.
36 *
37 * <p>The reason of this class it to share non-trivial code between BuildingState and NodeEntry. We
38 * could simply make those two classes extend this class instead, but we would be less
Janak Ramakrishnan2f55dd82015-08-03 19:08:11 +000039 * memory-efficient since object memory alignment does not cross classes (you would have two memory
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010040 * alignments, one for the base class and one for the extended one).
Janak Ramakrishnan2f55dd82015-08-03 19:08:11 +000041 *
42 * <p>This class is public only for the benefit of alternative graph implementations outside of the
43 * package.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044 */
Janak Ramakrishnan2f55dd82015-08-03 19:08:11 +000045public abstract class ReverseDepsUtil<T> {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010046
47 static final int MAYBE_CHECK_THRESHOLD = 10;
48
49 abstract void setReverseDepsObject(T container, Object object);
50
51 abstract void setSingleReverseDep(T container, boolean singleObject);
52
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000053 abstract void setDataToConsolidate(T container, @Nullable List<Object> dataToConsolidate);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054
55 abstract Object getReverseDepsObject(T container);
56
57 abstract boolean isSingleReverseDep(T container);
58
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000059 abstract List<Object> getDataToConsolidate(T container);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000060
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000061 private enum ConsolidateOp {
62 CHECK,
63 ADD,
64 REMOVE
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000065 }
66
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000067 /**
68 * Opaque container for a pending operation on the reverse deps set. We use subclasses to save
69 * 8 bytes of memory instead of keeping a field in this class, and we store
70 * {@link ConsolidateOp#CHECK} operations as the bare {@link SkyKey} in order to save the wrapper
71 * object in that case.
72 */
73 private abstract static class KeyToConsolidate {
74 // Do not access directly -- use the {@link #key} static accessor instead.
75 private final SkyKey key;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000076
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000077 /** Do not call directly -- use the {@link #create} static method instead. */
78 private KeyToConsolidate(SkyKey key) {
79 this.key = key;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000080 }
81
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000082 @Override
83 public String toString() {
84 return MoreObjects.toStringHelper(this).add("key", key).toString();
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000085 }
86
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000087 /** Gets which operation was delayed for the given object. */
88 static ConsolidateOp op(Object obj) {
89 if (obj instanceof SkyKey) {
90 return ConsolidateOp.CHECK;
91 }
92 if (obj instanceof KeyToRemove) {
93 return ConsolidateOp.REMOVE;
94 }
95 Preconditions.checkState(obj instanceof KeyToAdd, obj);
96 return ConsolidateOp.ADD;
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +000097 }
98
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +000099 /** Gets the key whose operation was delayed for the given object. */
100 static SkyKey key(Object obj) {
101 if (obj instanceof SkyKey) {
102 return (SkyKey) obj;
103 }
104 Preconditions.checkState(obj instanceof KeyToConsolidate, obj);
105 return ((KeyToConsolidate) obj).key;
106 }
107
108 static Object create(SkyKey key, ConsolidateOp op) {
109 switch (op) {
110 case CHECK:
111 return key;
112 case REMOVE:
113 return new KeyToRemove(key);
114 case ADD:
115 return new KeyToAdd(key);
116 default:
117 throw new IllegalStateException(op + ", " + key);
118 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000119 }
120 }
121
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000122 private static final class KeyToAdd extends KeyToConsolidate {
123 KeyToAdd(SkyKey key) {
124 super(key);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000125 }
126 }
127
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000128 private static final class KeyToRemove extends KeyToConsolidate {
129 KeyToRemove(SkyKey key) {
130 super(key);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000131 }
132 }
133
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000134 private void maybeDelayReverseDepOp(T container, Iterable<SkyKey> reverseDeps, ConsolidateOp op) {
135 List<Object> consolidations = getDataToConsolidate(container);
136 int currentReverseDepSize = getCurrentReverseDepSize(container);
137 if (consolidations == null) {
138 consolidations = new ArrayList<>(currentReverseDepSize);
139 setDataToConsolidate(container, consolidations);
140 }
141 for (SkyKey reverseDep : reverseDeps) {
142 consolidations.add(KeyToConsolidate.create(reverseDep, op));
143 }
144 // TODO(janakr): Should we consolidate more aggressively? This threshold can be customized.
145 if (consolidations.size() == currentReverseDepSize) {
146 consolidateData(container);
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000147 }
148 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100149
150 /**
151 * We check that the reverse dependency is not already present. We only do that if reverseDeps is
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000152 * small, so that it does not impact performance. We do not check if there are delayed data to
153 * consolidate, since then presence or absence is not known.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100154 */
155 void maybeCheckReverseDepNotPresent(T container, SkyKey reverseDep) {
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000156 if (getDataToConsolidate(container) != null) {
157 return;
158 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100159 if (isSingleReverseDep(container)) {
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000160 Preconditions.checkState(
161 !getReverseDepsObject(container).equals(reverseDep),
162 "Reverse dep %s already present in %s",
163 reverseDep,
164 container);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100165 return;
166 }
167 @SuppressWarnings("unchecked")
168 List<SkyKey> asList = (List<SkyKey>) getReverseDepsObject(container);
169 if (asList.size() < MAYBE_CHECK_THRESHOLD) {
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000170 Preconditions.checkState(
171 !asList.contains(reverseDep),
172 "Reverse dep %s already present in %s for %s",
173 reverseDep,
174 asList,
175 container);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100176 }
177 }
178
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000179 private int getCurrentReverseDepSize(T container) {
180 return isSingleReverseDep(container)
181 ? 1
182 : ((List<SkyKey>) getReverseDepsObject(container)).size();
183 }
184
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100185 /**
186 * We use a memory-efficient trick to keep reverseDeps memory usage low. Edges in Bazel are
187 * dominant over the number of nodes.
188 *
189 * <p>Most of the nodes have zero or one reverse dep. That is why we use immutable versions of the
190 * lists for those cases. In case of the size being > 1 we switch to an ArrayList. That is because
191 * we also have a decent number of nodes for which the reverseDeps are huge (for example almost
192 * everything depends on BuildInfo node).
193 *
194 * <p>We also optimize for the case where we have only one dependency. In that case we keep the
195 * object directly instead of a wrapper list.
196 */
197 @SuppressWarnings("unchecked")
Janak Ramakrishnan2f55dd82015-08-03 19:08:11 +0000198 public void addReverseDeps(T container, Collection<SkyKey> newReverseDeps) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100199 if (newReverseDeps.isEmpty()) {
200 return;
201 }
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000202 List<Object> dataToConsolidate = getDataToConsolidate(container);
203 if (dataToConsolidate != null) {
204 maybeDelayReverseDepOp(container, newReverseDeps, ConsolidateOp.ADD);
205 return;
206 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100207 Object reverseDeps = getReverseDepsObject(container);
208 int reverseDepsSize = isSingleReverseDep(container) ? 1 : ((List<SkyKey>) reverseDeps).size();
209 int newSize = reverseDepsSize + newReverseDeps.size();
210 if (newSize == 1) {
211 overwriteReverseDepsWithObject(container, Iterables.getOnlyElement(newReverseDeps));
212 } else if (reverseDepsSize == 0) {
213 overwriteReverseDepsList(container, Lists.newArrayList(newReverseDeps));
214 } else if (reverseDepsSize == 1) {
215 List<SkyKey> newList = Lists.newArrayListWithExpectedSize(newSize);
216 newList.add((SkyKey) reverseDeps);
217 newList.addAll(newReverseDeps);
218 overwriteReverseDepsList(container, newList);
219 } else {
220 ((List<SkyKey>) reverseDeps).addAll(newReverseDeps);
221 }
222 }
223
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000224 void checkReverseDep(T container, SkyKey reverseDep) {
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000225 maybeDelayReverseDepOp(container, ImmutableList.of(reverseDep), ConsolidateOp.CHECK);
Janak Ramakrishnan7ad99cb2015-09-10 20:35:21 +0000226 }
227
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100228 /**
229 * See {@code addReverseDeps} method.
230 */
231 void removeReverseDep(T container, SkyKey reverseDep) {
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000232 maybeDelayReverseDepOp(container, ImmutableList.of(reverseDep), ConsolidateOp.REMOVE);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100233 }
234
235 ImmutableSet<SkyKey> getReverseDeps(T container) {
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000236 consolidateData(container);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100237
238 // TODO(bazel-team): Unfortunately, we need to make a copy here right now to be on the safe side
239 // wrt. thread-safety. The parents of a node get modified when any of the parents is deleted,
240 // and we can't handle that right now.
241 if (isSingleReverseDep(container)) {
242 return ImmutableSet.of((SkyKey) getReverseDepsObject(container));
243 } else {
244 @SuppressWarnings("unchecked")
245 List<SkyKey> reverseDeps = (List<SkyKey>) getReverseDepsObject(container);
246 ImmutableSet<SkyKey> set = ImmutableSet.copyOf(reverseDeps);
247 Preconditions.checkState(set.size() == reverseDeps.size(),
248 "Duplicate reverse deps present in %s: %s. %s", this, reverseDeps, container);
249 return set;
250 }
251 }
252
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000253 private void consolidateData(T container) {
254 List<Object> dataToConsolidate = getDataToConsolidate(container);
255 if (dataToConsolidate == null) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100256 return;
257 }
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000258 setDataToConsolidate(container, null);
Janak Ramakrishnan441dcb12015-10-09 22:44:31 +0000259 Object reverseDeps = getReverseDepsObject(container);
260 if (isSingleReverseDep(container)) {
261 Preconditions.checkState(
262 dataToConsolidate.size() == 1,
263 "dataToConsolidate not size 1 even though only one rdep: %s %s %s",
264 dataToConsolidate,
265 reverseDeps,
266 container);
267 Object keyToConsolidate = Iterables.getOnlyElement(dataToConsolidate);
268 SkyKey key = KeyToConsolidate.key(keyToConsolidate);
269 switch (KeyToConsolidate.op(keyToConsolidate)) {
270 case REMOVE:
271 overwriteReverseDepsList(container, ImmutableList.<SkyKey>of());
272 // Fall through to check.
273 case CHECK:
274 Preconditions.checkState(
275 key.equals(reverseDeps), "%s %s %s", keyToConsolidate, reverseDeps, container);
276 break;
277 case ADD:
278 throw new IllegalStateException(
279 "Shouldn't delay add if only one element: "
280 + keyToConsolidate
281 + ", "
282 + reverseDeps
283 + ", "
284 + container);
285 default:
286 throw new IllegalStateException(keyToConsolidate + ", " + reverseDeps + ", " + container);
287 }
288 return;
289 }
290 List<SkyKey> reverseDepsAsList = (List<SkyKey>) reverseDeps;
291 Set<SkyKey> reverseDepsAsSet = CompactHashSet.create(reverseDepsAsList);
292
293 if (reverseDepsAsSet.size() != reverseDepsAsList.size()) {
294 // We're about to crash. Try to print an informative error message.
295 Set<SkyKey> seen = new HashSet<>();
296 List<SkyKey> duplicates = new ArrayList<>();
297 for (SkyKey key : reverseDepsAsList) {
298 if (!seen.add(key)) {
299 duplicates.add(key);
300 }
301 }
302 throw new IllegalStateException(
303 (reverseDepsAsList.size() - reverseDepsAsSet.size())
304 + " duplicates: "
305 + duplicates
306 + " for "
307 + container);
308 }
309 for (Object keyToConsolidate : dataToConsolidate) {
310 SkyKey key = KeyToConsolidate.key(keyToConsolidate);
311 switch (KeyToConsolidate.op(keyToConsolidate)) {
312 case CHECK:
313 Preconditions.checkState(
314 reverseDepsAsSet.contains(key),
315 "%s %s %s",
316 keyToConsolidate,
317 reverseDepsAsSet,
318 container);
319 break;
320 case REMOVE:
321 Preconditions.checkState(
322 reverseDepsAsSet.remove(key), "%s %s %s", keyToConsolidate, reverseDeps, container);
323 break;
324 case ADD:
325 Preconditions.checkState(
326 reverseDepsAsSet.add(key), "%s %s %s", keyToConsolidate, reverseDeps, container);
327 break;
328 default:
329 throw new IllegalStateException(
330 keyToConsolidate + ", " + reverseDepsAsSet + ", " + container);
331 }
332 }
333
334 if (reverseDepsAsSet.isEmpty()) {
335 overwriteReverseDepsList(container, ImmutableList.<SkyKey>of());
336 } else if (reverseDepsAsSet.size() == 1) {
337 overwriteReverseDepsWithObject(container, Iterables.getOnlyElement(reverseDepsAsSet));
338 } else {
339 overwriteReverseDepsList(container, new ArrayList<>(reverseDepsAsSet));
340 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100341 }
342
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100343 String toString(T container) {
Googler2c19a572015-07-16 08:38:49 +0000344 return MoreObjects.toStringHelper("ReverseDeps")
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100345 .add("reverseDeps", getReverseDepsObject(container))
346 .add("singleReverseDep", isSingleReverseDep(container))
Janak Ramakrishnan5877b8b2015-09-22 17:37:10 +0000347 .add("dataToConsolidate", getDataToConsolidate(container))
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100348 .toString();
349 }
350
351 private void overwriteReverseDepsWithObject(T container, SkyKey newObject) {
352 setReverseDepsObject(container, newObject);
353 setSingleReverseDep(container, true);
354 }
355
356 private void overwriteReverseDepsList(T container, List<SkyKey> list) {
357 setReverseDepsObject(container, list);
358 setSingleReverseDep(container, false);
359 }
360}