aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/collect
diff options
context:
space:
mode:
authorGravatar cpeyser <cpeyser@google.com>2018-06-13 13:06:17 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-06-13 13:07:41 -0700
commit9bbc66c8613deb4233e3cb116d0aae5724eece7f (patch)
treec1a95e1cb6b0370a616f5d9daf9ce966a6d6fea0 /src/main/java/com/google/devtools/build/lib/collect
parentd6bd9eb43b8e4b47acd4e5003b586bd47edaba4e (diff)
Allow deserialization futures as NestedSet contents, with unrolling blocking on that future.
This allows NestedSet deserialization not to block on storage reads - in-progress deserializations are simply made a member of the NestedSets they produce. PiperOrigin-RevId: 200440131
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/collect')
-rw-r--r--src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java140
-rw-r--r--src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTestUtils.java26
-rw-r--r--src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecWithStore.java86
-rw-r--r--src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetFingerprintCache.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetStore.java128
-rw-r--r--src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetView.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetVisitor.java2
7 files changed, 280 insertions, 106 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
index 52df58e36a..5371c306eb 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSet.java
@@ -19,6 +19,7 @@ import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.devtools.build.lib.collect.compacthashset.CompactHashSet;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.protobuf.ByteString;
@@ -29,6 +30,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
/**
@@ -55,9 +57,7 @@ public final class NestedSet<E> implements Iterable<E> {
private static final byte[] LEAF_MEMO = {};
@AutoCodec static final Object[] EMPTY_CHILDREN = {};
- /**
- * Construct an empty NestedSet. Should only be called by Order's class initializer.
- */
+ /** Construct an empty NestedSet. Should only be called by Order's class initializer. */
NestedSet(Order order) {
this.orderAndSize = order.ordinal();
this.children = EMPTY_CHILDREN;
@@ -116,7 +116,8 @@ public final class NestedSet<E> implements Iterable<E> {
} else if ((pass == 1) == preorder && !transitive.isEmpty()) {
CompactHashSet<E> hoisted = CompactHashSet.create();
for (NestedSet<E> subset : transitiveOrder) {
- Object c = subset.children;
+ // If this is a deserialization future, this call blocks.
+ Object c = subset.getChildren();
if (c instanceof Object[]) {
Object[] a = (Object[]) c;
if (a.length < 2) {
@@ -149,63 +150,93 @@ public final class NestedSet<E> implements Iterable<E> {
}
}
- // Only used by deserialization
- @AutoCodec.Instantiator
- NestedSet(Order order, Object children) {
+ private NestedSet(Order order, Object children, byte[] memo) {
this.orderAndSize = order.ordinal();
this.children = children;
+ this.memo = memo;
+ }
+
+ /**
+ * Constructs a NestedSet that is currently being deserialized. The provided future, when
+ * complete, gives the contents of the NestedSet.
+ */
+ static <E> NestedSet<E> withFuture(
+ Order order, ListenableFuture<Object[]> deserializationFuture) {
+ return new NestedSet<>(order, deserializationFuture, /*memo=*/ null);
+ }
+
+ // Only used by deserialization
+ @AutoCodec.Instantiator
+ static <E> NestedSet<E> forDeserialization(Order order, Object children) {
+ Preconditions.checkState(!(children instanceof ListenableFuture));
boolean hasChildren =
children instanceof Object[]
&& (Arrays.stream((Object[]) children).anyMatch(child -> child instanceof Object[]));
- this.memo = hasChildren ? null : LEAF_MEMO;
+ byte[] memo = hasChildren ? null : LEAF_MEMO;
+ return new NestedSet<>(order, children, memo);
}
- /**
- * Returns the ordering of this nested set.
- */
+ /** Returns the ordering of this nested set. */
public Order getOrder() {
return Order.getOrder(orderAndSize & 3);
}
/**
- * Returns the internal item or array. For use by NestedSetVisitor and NestedSetView. Those two
- * classes also have knowledge of the internal implementation of NestedSet.
+ * Returns the internal item or array. If the internal item is a deserialization future, blocks on
+ * completion. For external use only by NestedSetVisitor and NestedSetView. Those two classes also
+ * have knowledge of the internal implementation of NestedSet.
*/
+ Object getChildren() {
+ if (children instanceof ListenableFuture) {
+ try {
+ return ((ListenableFuture<Object[]>) children).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+ } else {
+ return children;
+ }
+ }
+
+ /** Returns the internal item, array, or future. */
Object rawChildren() {
return children;
}
- /**
- * Returns true if the set is empty. Runs in O(1) time (i.e. does not flatten the set).
- */
+ /** Returns true if the set is empty. Runs in O(1) time (i.e. does not flatten the set). */
public boolean isEmpty() {
+ // We don't check for future members here, since empty sets are special-cased in serialization
+ // and do not make requests against storage.
return children == EMPTY_CHILDREN;
}
/** Returns true if the set has exactly one element. */
public boolean isSingleton() {
- return !(children instanceof Object[]);
+ // Singleton sets are special cased in serialization, and make no calls to storage. Therefore,
+ // we know that any NestedSet with a ListenableFuture member is not a singleton.
+ return !(children instanceof Object[] || children instanceof ListenableFuture);
}
/**
- * Returns a collection of all unique elements of this set (including subsets)
- * in an implementation-specified order as a {@code Collection}.
+ * Returns a collection of all unique elements of this set (including subsets) in an
+ * implementation-specified order as a {@code Collection}.
*
- * <p>If you do not need a Collection and an Iterable is enough, use the
- * nested set itself as an Iterable.
+ * <p>If you do not need a Collection and an Iterable is enough, use the nested set itself as an
+ * Iterable.
*/
public Collection<E> toCollection() {
return toList();
}
/**
- * Returns a collection of all unique elements of this set (including subsets)
- * in an implementation-specified order as a {code List}.
+ * Returns a collection of all unique elements of this set (including subsets) in an
+ * implementation-specified order as a {code List}.
*
* <p>Use {@link #toCollection} when possible for better efficiency.
*/
public List<E> toList() {
if (isSingleton()) {
+ // No need to check for ListenableFuture members - singletons can't have them.
return ImmutableList.of((E) children);
}
if (isEmpty()) {
@@ -215,8 +246,8 @@ public final class NestedSet<E> implements Iterable<E> {
}
/**
- * Returns a collection of all unique elements of this set (including subsets)
- * in an implementation-specified order as a {@code Set}.
+ * Returns a collection of all unique elements of this set (including subsets) in an
+ * implementation-specified order as a {@code Set}.
*
* <p>Use {@link #toCollection} when possible for better efficiency.
*/
@@ -225,11 +256,13 @@ public final class NestedSet<E> implements Iterable<E> {
}
/**
- * Returns true if this set is equal to {@code other} based on the top-level
- * elements and object identity (==) of direct subsets. As such, this function
- * can fail to equate {@code this} with another {@code NestedSet} that holds
- * the same elements. It will never fail to detect that two {@code NestedSet}s
- * are different, however.
+ * Returns true if this set is equal to {@code other} based on the top-level elements and object
+ * identity (==) of direct subsets. As such, this function can fail to equate {@code this} with
+ * another {@code NestedSet} that holds the same elements. It will never fail to detect that two
+ * {@code NestedSet}s are different, however.
+ *
+ * <p>If one of the sets is in the process of deserialization, returns true iff both sets depend
+ * on the same future.
*
* @param other the {@code NestedSet} to compare against.
*/
@@ -237,25 +270,28 @@ public final class NestedSet<E> implements Iterable<E> {
if (this == other) {
return true;
}
+
return other != null
&& getOrder() == other.getOrder()
- && (children.equals(other.children)
+ && (rawChildren().equals(other.rawChildren())
|| (!isSingleton()
&& !other.isSingleton()
+ && rawChildren() instanceof Object[]
+ && other.rawChildren() instanceof Object[]
&& Arrays.equals((Object[]) children, (Object[]) other.children)));
}
/**
- * Returns a hash code that produces a notion of identity that is consistent with
- * {@link #shallowEquals}. In other words, if two {@code NestedSet}s are equal according
- * to {@code #shallowEquals}, then they return the same {@code shallowHashCode}.
+ * Returns a hash code that produces a notion of identity that is consistent with {@link
+ * #shallowEquals}. In other words, if two {@code NestedSet}s are equal according to {@code
+ * #shallowEquals}, then they return the same {@code shallowHashCode}.
*
- * <p>The main reason for having these separate functions instead of reusing
- * the standard equals/hashCode is to minimize accidental use, since they are
- * different from both standard Java objects and collection-like objects.
+ * <p>The main reason for having these separate functions instead of reusing the standard
+ * equals/hashCode is to minimize accidental use, since they are different from both standard Java
+ * objects and collection-like objects.
*/
public int shallowHashCode() {
- return isSingleton()
+ return isSingleton() || children instanceof ListenableFuture
? Objects.hash(getOrder(), children)
: Objects.hash(getOrder(), Arrays.hashCode((Object[]) children));
}
@@ -278,7 +314,9 @@ public final class NestedSet<E> implements Iterable<E> {
private enum Stringer implements Function<Object, String> {
INSTANCE;
- @Override public String apply(Object o) {
+
+ @Override
+ public String apply(Object o) {
return childrenToString(o);
}
}
@@ -290,9 +328,9 @@ public final class NestedSet<E> implements Iterable<E> {
}
/**
- * Implementation of {@link #toList}. Uses one of three strategies based on the value of
- * {@code this.memo}: wrap our direct items in a list, call {@link #lockedExpand} to perform
- * the initial {@link #walk}, or call {@link #replay} if we have a nontrivial memo.
+ * Implementation of {@link #toList}. Uses one of three strategies based on the value of {@code
+ * this.memo}: wrap our direct items in a list, call {@link #lockedExpand} to perform the initial
+ * {@link #walk}, or call {@link #replay} if we have a nontrivial memo.
*/
private ImmutableList<E> expand() {
// This value is only set in the constructor, so safe to test here with no lock.
@@ -303,7 +341,7 @@ public final class NestedSet<E> implements Iterable<E> {
if (members != null) {
return ImmutableList.copyOf(members);
}
- Object[] children = (Object[]) this.children;
+ Object[] children = (Object[]) this.getChildren();
ImmutableList.Builder<E> output = ImmutableList.builderWithExpectedSize(orderAndSize >> 2);
replay(output, children, memo, 0);
return output.build();
@@ -316,26 +354,32 @@ public final class NestedSet<E> implements Iterable<E> {
ArraySharingCollection(Object[] array) {
this.array = array;
}
- @Override public Object[] toArray() {
+
+ @Override
+ public Object[] toArray() {
return array;
}
- @Override public int size() {
+
+ @Override
+ public int size() {
return array.length;
}
- @Override public Iterator<E> iterator() {
+
+ @Override
+ public Iterator<E> iterator() {
throw new UnsupportedOperationException();
}
}
/**
* If this is the first call for this object, fills {@code this.memo} and returns a set from
- * {@link #walk}. Otherwise returns null; the caller should use {@link #replay} instead.
+ * {@link #walk}. Otherwise returns null; the caller should use {@link #replay} instead.
*/
private synchronized CompactHashSet<E> lockedExpand() {
if (memo != null) {
return null;
}
- Object[] children = (Object[]) this.children;
+ Object[] children = (Object[]) this.getChildren();
CompactHashSet<E> members = CompactHashSet.createWithExpectedSize(128);
CompactHashSet<Object> sets = CompactHashSet.createWithExpectedSize(128);
sets.add(children);
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTestUtils.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTestUtils.java
index ea455f87e1..36171b5cdc 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTestUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTestUtils.java
@@ -23,6 +23,7 @@ import com.google.devtools.build.lib.skyframe.serialization.SerializationExcepti
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester;
+import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester.VerificationFunction;
import java.io.IOException;
/** Utilities for testing NestedSet serialization. */
@@ -49,7 +50,7 @@ public class NestedSetCodecTestUtils {
return false;
}
HasNestedSet that = (HasNestedSet) o;
- return Objects.equal(nestedSetField.rawChildren(), that.nestedSetField.rawChildren());
+ return Objects.equal(nestedSetField.getChildren(), that.nestedSetField.getChildren());
}
@Override
@@ -59,7 +60,8 @@ public class NestedSetCodecTestUtils {
}
/** Perform serialization/deserialization checks for several simple NestedSet examples. */
- public static void checkCodec(ObjectCodecs objectCodecs, boolean allowFutureBlocking)
+ public static void checkCodec(
+ ObjectCodecs objectCodecs, boolean allowFutureBlocking, boolean assertSymmetricEquality)
throws Exception {
new SerializationTester(
NestedSetBuilder.emptySet(Order.STABLE_ORDER),
@@ -86,7 +88,7 @@ public class NestedSetCodecTestUtils {
new HasNestedSet(NestedSetBuilder.create(Order.STABLE_ORDER, "a"))))
.setObjectCodecs(objectCodecs)
.makeMemoizingAndAllowFutureBlocking(allowFutureBlocking)
- .setVerificationFunction(NestedSetCodecTestUtils::verifyDeserialization)
+ .setVerificationFunction(verificationFunction(assertSymmetricEquality))
.runTests();
}
@@ -94,15 +96,21 @@ public class NestedSetCodecTestUtils {
NestedSetStore store, NestedSet<?> nestedSet, SerializationContext serializationContext)
throws IOException, SerializationException {
return store
- .computeFingerprintAndStore((Object[]) nestedSet.rawChildren(), serializationContext)
+ .computeFingerprintAndStore((Object[]) nestedSet.getChildren(), serializationContext)
.writeStatus();
}
- private static void verifyDeserialization(
- NestedSet<String> subject, NestedSet<String> deserialized) {
- assertThat(subject.getOrder()).isEqualTo(deserialized.getOrder());
- assertThat(subject.toSet()).isEqualTo(deserialized.toSet());
- verifyStructure(subject.rawChildren(), deserialized.rawChildren());
+ private static VerificationFunction<NestedSet<String>> verificationFunction(
+ boolean assertSymmetricEquality) {
+ return (subject, deserialized) -> {
+ if (assertSymmetricEquality) {
+ assertThat(subject).isEqualTo(deserialized);
+ assertThat(deserialized).isEqualTo(subject);
+ }
+ assertThat(subject.getOrder()).isEqualTo(deserialized.getOrder());
+ assertThat(subject.toSet()).isEqualTo(deserialized.toSet());
+ verifyStructure(subject.getChildren(), deserialized.getChildren());
+ };
}
private static void verifyStructure(Object lhs, Object rhs) {
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecWithStore.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecWithStore.java
index 081fd674f8..acd7ec5931 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecWithStore.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecWithStore.java
@@ -15,6 +15,8 @@ package com.google.devtools.build.lib.collect.nestedset;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.devtools.build.lib.collect.nestedset.NestedSetStore.FingerprintComputationResult;
import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
@@ -86,11 +88,11 @@ public class NestedSetCodecWithStore<T> implements ObjectCodec<NestedSet<T>> {
} else if (obj.isSingleton()) {
// If the NestedSet is a singleton, we serialize directly as an optimization.
codedOut.writeEnumNoTag(NestedSetSize.SINGLETON.ordinal());
- context.serialize(obj.rawChildren(), codedOut);
+ context.serialize(obj.getChildren(), codedOut);
} else {
codedOut.writeEnumNoTag(NestedSetSize.GROUP.ordinal());
FingerprintComputationResult fingerprintComputationResult =
- nestedSetStore.computeFingerprintAndStore((Object[]) obj.rawChildren(), context);
+ nestedSetStore.computeFingerprintAndStore((Object[]) obj.getChildren(), context);
context.addFutureToBlockWritingOn(fingerprintComputationResult.writeStatus());
codedOut.writeByteArrayNoTag(fingerprintComputationResult.fingerprint().toByteArray());
}
@@ -115,8 +117,9 @@ public class NestedSetCodecWithStore<T> implements ObjectCodec<NestedSet<T>> {
return intern(order, contents);
case GROUP:
ByteString fingerprint = ByteString.copyFrom(codedIn.readByteArray());
- Object members = nestedSetStore.getContentsAndDeserialize(fingerprint, context);
- return intern(order, members);
+ ListenableFuture<Object[]> deserializationFuture =
+ nestedSetStore.getContentsAndDeserialize(fingerprint, context);
+ return intern(order, deserializationFuture);
}
throw new IllegalStateException("NestedSet size " + nestedSetSize + " not known");
}
@@ -125,20 +128,31 @@ public class NestedSetCodecWithStore<T> implements ObjectCodec<NestedSet<T>> {
* Morally, NestedSets are compared using reference equality, to avoid the cost of unrolling them.
* However, when deserializing NestedSet, we don't want to end up with two sets that "should" be
* reference-equal, but are not. Since our codec implementation caches the underlying {@link
- * NestedSet#children} Object[], two nested sets that should be the same will have equal
- * underlying {@link NestedSet#children}, so we can use that for an equality check.
+ * NestedSet#getChildren()} Object[], two nested sets that should be the same will have equal
+ * underlying {@link NestedSet#getChildren()}, so we can use that for an equality check.
+ *
+ * <p>We also would like to prevent the existence of two equal NestedSets in a single JVM, in
+ * which one NestedSet contains an Object[] and the other contains a ListenableFuture<Object[]>
+ * for the same contents. This can happen if a NestedSet is serialized, and then deserialized with
+ * a call to storage for those contents. In order to guarantee that only one NestedSet exists for
+ * a given Object[], the interner checks the doneness of any ListenableFuture, and if done, holds
+ * on only to the "materialized" NestedSet, with the Object[] as its children object.
*
* <p>Note that singleton NestedSets' underlying children are not cached, but we must still
* enforce equality for them. To do that, we use the #hashCode and #equals of the {@link
- * NestedSet#children}. When that field is an Object[], this is just identity hash code and
+ * NestedSet#getChildren()}. When that field is an Object[], this is just identity hash code and
* reference equality, but when it is something else (like an Artifact), we will do an actual
* equality comparison. This may make some singleton NestedSets reference-equal where they were
* not before. This should be ok as long as the contained object properly implements equality.
- *
- * <p>TODO(janakr): Fix this bug.
*/
+ @SuppressWarnings("unchecked")
private NestedSet<T> intern(Order order, Object contents) {
- NestedSet<T> result = new NestedSet<>(order, contents);
+ NestedSet<T> result;
+ if (contents instanceof ListenableFuture) {
+ result = NestedSet.withFuture(order, (ListenableFuture<Object[]>) contents);
+ } else {
+ result = NestedSet.forDeserialization(order, contents);
+ }
try {
return interner.get(new EqualsWrapper<>(result), () -> result);
} catch (ExecutionException e) {
@@ -153,11 +167,38 @@ public class NestedSetCodecWithStore<T> implements ObjectCodec<NestedSet<T>> {
this.nestedSet = nestedSet;
}
+ @SuppressWarnings("unchecked")
@Override
public int hashCode() {
- return 37 * nestedSet.getOrder().hashCode() + nestedSet.rawChildren().hashCode();
+ int childrenHashCode;
+ if (nestedSet.rawChildren() instanceof ListenableFuture
+ && ((ListenableFuture) nestedSet.rawChildren()).isDone()) {
+ try {
+ childrenHashCode = Futures.getDone((ListenableFuture) nestedSet.rawChildren()).hashCode();
+ } catch (ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+ } else {
+ childrenHashCode = nestedSet.rawChildren().hashCode();
+ }
+
+ return 37 * nestedSet.getOrder().hashCode() + childrenHashCode;
}
+ private static boolean deserializingAndMaterializedSetsAreEqual(
+ Object[] contents, ListenableFuture<Object[]> contentsFuture) {
+ if (!contentsFuture.isDone()) {
+ return false;
+ }
+
+ try {
+ return Futures.getDone(contentsFuture) == contents;
+ } catch (ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -166,9 +207,28 @@ public class NestedSetCodecWithStore<T> implements ObjectCodec<NestedSet<T>> {
if (!(obj instanceof EqualsWrapper)) {
return false;
}
+
+ // Both sets contain Object[] or both sets contain ListenableFuture<Object[]>
NestedSet<?> thatSet = ((EqualsWrapper<?>) obj).nestedSet;
- return this.nestedSet.getOrder().equals(thatSet.getOrder())
- && this.nestedSet.rawChildren().equals(thatSet.rawChildren());
+ if (this.nestedSet.getOrder().equals(thatSet.getOrder())
+ && this.nestedSet.rawChildren().equals(thatSet.rawChildren())) {
+ return true;
+ }
+
+ // One set contains Object[], while the other contains ListenableFuture<Object[]>
+ if (this.nestedSet.rawChildren() instanceof ListenableFuture
+ && thatSet.rawChildren() instanceof Object[]) {
+ return deserializingAndMaterializedSetsAreEqual(
+ (Object[]) thatSet.rawChildren(),
+ (ListenableFuture<Object[]>) this.nestedSet.rawChildren());
+ } else if (thatSet.rawChildren() instanceof ListenableFuture
+ && this.nestedSet.rawChildren() instanceof Object[]) {
+ return deserializingAndMaterializedSetsAreEqual(
+ (Object[]) this.nestedSet.rawChildren(),
+ (ListenableFuture<Object[]>) thatSet.rawChildren());
+ } else {
+ return false;
+ }
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetFingerprintCache.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetFingerprintCache.java
index f0b1c2a8db..8eb4131793 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetFingerprintCache.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetFingerprintCache.java
@@ -53,7 +53,7 @@ public class NestedSetFingerprintCache {
}
DigestMap digestMap = mapFnToDigestMap.computeIfAbsent(mapFn, this::newDigestMap);
fingerprint.addInt(nestedSet.getOrder().ordinal());
- Object children = nestedSet.rawChildren();
+ Object children = nestedSet.getChildren();
addToFingerprint(mapFn, fingerprint, digestMap, children);
}
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetStore.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetStore.java
index 52d7769a2c..173420aa1d 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetStore.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetStore.java
@@ -31,7 +31,11 @@ import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import javax.annotation.Nullable;
/**
@@ -70,11 +74,12 @@ public class NestedSetStore {
/**
* Retrieves the serialized bytes for the NestedSet contents associated with this fingerprint.
*/
- byte[] get(ByteString fingerprint) throws IOException;
+ ListenableFuture<byte[]> get(ByteString fingerprint) throws IOException;
}
/** An in-memory {@link NestedSetStorageEndpoint} */
- private static class InMemoryNestedSetStorageEndpoint implements NestedSetStorageEndpoint {
+ @VisibleForTesting
+ static class InMemoryNestedSetStorageEndpoint implements NestedSetStorageEndpoint {
private final ConcurrentHashMap<ByteString, byte[]> fingerprintToContents =
new ConcurrentHashMap<>();
@@ -85,14 +90,15 @@ public class NestedSetStore {
}
@Override
- public byte[] get(ByteString fingerprint) {
- return fingerprintToContents.get(fingerprint);
+ public ListenableFuture<byte[]> get(ByteString fingerprint) {
+ return Futures.immediateFuture(fingerprintToContents.get(fingerprint));
}
}
/** An in-memory cache for fingerprint <-> NestedSet associations. */
- private static class NestedSetCache {
- private final Cache<ByteString, Object[]> fingerprintToContents =
+ @VisibleForTesting
+ static class NestedSetCache {
+ private final Cache<ByteString, ListenableFuture<Object[]>> fingerprintToContents =
CacheBuilder.newBuilder()
.concurrencyLevel(SerializationConstants.DESERIALIZATION_POOL_SIZE)
.weakValues()
@@ -110,7 +116,7 @@ public class NestedSetStore {
* fingerprint is not known.
*/
@Nullable
- public Object[] contentsForFingerprint(ByteString fingerprint) {
+ public ListenableFuture<Object[]> contentsForFingerprint(ByteString fingerprint) {
return fingerprintToContents.getIfPresent(fingerprint);
}
@@ -124,9 +130,29 @@ public class NestedSetStore {
}
/** Associates the provided fingerprint and NestedSet contents. */
+ public void put(
+ FingerprintComputationResult fingerprintComputationResult,
+ ListenableFuture<Object[]> contents) {
+ contents.addListener(
+ () -> {
+ try {
+ contentsToFingerprint.put(Futures.getDone(contents), fingerprintComputationResult);
+ } catch (ExecutionException e) {
+ throw new AssertionError(
+ "Expected write for "
+ + fingerprintComputationResult.fingerprint()
+ + " to be complete",
+ e.getCause());
+ }
+ },
+ MoreExecutors.directExecutor());
+ fingerprintToContents.put(fingerprintComputationResult.fingerprint(), contents);
+ }
+
public void put(FingerprintComputationResult fingerprintComputationResult, Object[] contents) {
contentsToFingerprint.put(contents, fingerprintComputationResult);
- fingerprintToContents.put(fingerprintComputationResult.fingerprint(), contents);
+ fingerprintToContents.put(
+ fingerprintComputationResult.fingerprint(), Futures.immediateFuture(contents));
}
}
@@ -145,12 +171,31 @@ public class NestedSetStore {
public abstract ListenableFuture<Void> writeStatus();
}
- private final NestedSetCache nestedSetCache = new NestedSetCache();
+ private final NestedSetCache nestedSetCache;
private final NestedSetStorageEndpoint nestedSetStorageEndpoint;
+ private final Executor executor;
/** Creates a NestedSetStore with the provided {@link NestedSetStorageEndpoint} as a backend. */
public NestedSetStore(NestedSetStorageEndpoint nestedSetStorageEndpoint) {
+ this(nestedSetStorageEndpoint, new NestedSetCache(), MoreExecutors.directExecutor());
+ }
+
+ /**
+ * Creates a NestedSetStore with the provided {@link NestedSetStorageEndpoint} and executor for
+ * deserialization.
+ */
+ public NestedSetStore(NestedSetStorageEndpoint nestedSetStorageEndpoint, Executor executor) {
+ this(nestedSetStorageEndpoint, new NestedSetCache(), executor);
+ }
+
+ @VisibleForTesting
+ public NestedSetStore(
+ NestedSetStorageEndpoint nestedSetStorageEndpoint,
+ NestedSetCache nestedSetCache,
+ Executor executor) {
this.nestedSetStorageEndpoint = nestedSetStorageEndpoint;
+ this.nestedSetCache = nestedSetCache;
+ this.executor = executor;
}
/** Creates a NestedSetStore with an in-memory storage backend. */
@@ -226,37 +271,54 @@ public class NestedSetStore {
}
/** Retrieves and deserializes the NestedSet contents associated with the given fingerprint. */
- public Object[] getContentsAndDeserialize(
- ByteString fingerprint, DeserializationContext deserializationContext)
- throws SerializationException, IOException {
- Object[] contents = nestedSetCache.contentsForFingerprint(fingerprint);
+ public ListenableFuture<Object[]> getContentsAndDeserialize(
+ ByteString fingerprint, DeserializationContext deserializationContext) throws IOException {
+ ListenableFuture<Object[]> contents = nestedSetCache.contentsForFingerprint(fingerprint);
if (contents != null) {
return contents;
}
+ ListenableFuture<byte[]> retrieved = nestedSetStorageEndpoint.get(fingerprint);
+ ListenableFuture<Object[]> result =
+ Futures.transformAsync(
+ retrieved,
+ bytes -> {
+ CodedInputStream codedIn = CodedInputStream.newInstance(bytes);
+ int numberOfElements = codedIn.readInt32();
+ DeserializationContext newDeserializationContext =
+ deserializationContext.getNewMemoizingContext();
- byte[] retrieved = nestedSetStorageEndpoint.get(fingerprint);
- if (retrieved == null) {
- throw new AssertionError("Fingerprint " + fingerprint + " not found in NestedSetStore");
- }
+ // The elements of this list are futures for the deserialized values of these
+ // NestedSet contents. For direct members, the futures complete immediately and yield
+ // an Object. For transitive members (fingerprints), the futures complete with the
+ // underlying fetch, and yield Object[]s.
+ List<ListenableFuture<?>> deserializationFutures = new ArrayList<>();
+ for (int i = 0; i < numberOfElements; i++) {
+ Object deserializedElement = newDeserializationContext.deserialize(codedIn);
+ if (deserializedElement instanceof ByteString) {
+ deserializationFutures.add(
+ getContentsAndDeserialize(
+ (ByteString) deserializedElement, deserializationContext));
+ } else {
+ deserializationFutures.add(Futures.immediateFuture(deserializedElement));
+ }
+ }
- CodedInputStream codedIn = CodedInputStream.newInstance(retrieved);
- DeserializationContext newDeserializationContext =
- deserializationContext.getNewMemoizingContext();
-
- int numberOfElements = codedIn.readInt32();
- Object[] dereferencedContents = new Object[numberOfElements];
- for (int i = 0; i < numberOfElements; i++) {
- Object deserializedElement = newDeserializationContext.deserialize(codedIn);
- dereferencedContents[i] =
- deserializedElement instanceof ByteString
- ? getContentsAndDeserialize(
- (ByteString) deserializedElement, deserializationContext)
- : deserializedElement;
- }
+ return Futures.whenAllComplete(deserializationFutures)
+ .call(
+ () -> {
+ Object[] deserializedContents = new Object[deserializationFutures.size()];
+ for (int i = 0; i < deserializationFutures.size(); i++) {
+ deserializedContents[i] = Futures.getDone(deserializationFutures.get(i));
+ }
+ return deserializedContents;
+ },
+ executor);
+ },
+ executor);
FingerprintComputationResult fingerprintComputationResult =
FingerprintComputationResult.create(fingerprint, Futures.immediateFuture(null));
- nestedSetCache.put(fingerprintComputationResult, dereferencedContents);
- return dereferencedContents;
+ nestedSetCache.put(fingerprintComputationResult, result);
+ return result;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetView.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetView.java
index 2d81b8ef66..26487b0915 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetView.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetView.java
@@ -42,7 +42,7 @@ public class NestedSetView<E> {
/** Construct a view of a given NestedSet. */
public NestedSetView(NestedSet<E> set) {
- this(set.rawChildren());
+ this(set.getChildren());
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetVisitor.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetVisitor.java
index 090b8e33bb..c6e0e87133 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetVisitor.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetVisitor.java
@@ -59,7 +59,7 @@ public final class NestedSetVisitor<E> {
// We can short-circuit empty nested set visitation here, avoiding load on the shared map
// VisitedState#seenNodes.
if (!nestedSet.isEmpty()) {
- visitRaw(nestedSet.rawChildren());
+ visitRaw(nestedSet.getChildren());
}
}