blob: 7a4d87e6471c026466d087c12f5bd5e1065f9129 [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.query2;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.packages.Target;
import com.google.devtools.build.lib.query2.engine.Callback;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryTaskFuture;
import com.google.devtools.build.lib.query2.engine.QueryEnvironment.ThreadSafeMutableSet;
import com.google.devtools.build.lib.query2.engine.QueryException;
import com.google.devtools.build.lib.query2.engine.QueryExpression;
import com.google.devtools.build.lib.query2.engine.QueryExpressionContext;
import com.google.devtools.build.lib.query2.engine.QueryUtil;
import com.google.devtools.build.lib.query2.engine.QueryUtil.AggregateAllCallback;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.skyframe.SkyKey;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;
/**
* Parallel implementations of various functionality in {@link SkyQueryEnvironment}.
*
* <p>Special attention is given to memory usage. Naive parallel implementations of query
* functionality would lead to memory blowup. Instead of dealing with {@link Target}s, we try to
* deal with {@link SkyKey}s as much as possible to reduce the number of {@link Package}s forcibly
* in memory at any given time.
*/
// TODO(bazel-team): Be more deliberate about bounding memory usage here.
public class ParallelSkyQueryUtils {
/** The maximum number of keys to visit at once. */
@VisibleForTesting public static final int VISIT_BATCH_SIZE = 10000;
private ParallelSkyQueryUtils() {
}
static QueryTaskFuture<Void> getAllRdepsUnboundedParallel(
SkyQueryEnvironment env,
QueryExpression expression,
QueryExpressionContext<Target> context,
Callback<Target> callback) {
return env.eval(
expression,
context,
ParallelVisitorUtils.createParallelVisitorCallback(
new RdepsUnboundedVisitor.Factory(
env, /*unfilteredUniverse=*/ Predicates.alwaysTrue(), callback)));
}
static QueryTaskFuture<Void> getAllRdepsBoundedParallel(
SkyQueryEnvironment env,
QueryExpression expression,
int depth,
QueryExpressionContext<Target> context,
Callback<Target> callback) {
return env.eval(
expression,
context,
ParallelVisitorUtils.createParallelVisitorCallback(
new RdepsBoundedVisitor.Factory(
env, depth, /*universe=*/ Predicates.alwaysTrue(), callback)));
}
static QueryTaskFuture<Void> getRdepsInUniverseUnboundedParallel(
SkyQueryEnvironment env,
QueryExpression expression,
Predicate<SkyKey> unfilteredUniverse,
QueryExpressionContext<Target> context,
Callback<Target> callback) {
return env.eval(
expression,
context,
ParallelVisitorUtils.createParallelVisitorCallback(
new RdepsUnboundedVisitor.Factory(env, unfilteredUniverse, callback)));
}
static QueryTaskFuture<Predicate<SkyKey>> getDTCSkyKeyPredicateFuture(
SkyQueryEnvironment env,
QueryExpression expression,
QueryExpressionContext<Target> context,
int processResultsBatchSize,
int concurrencyLevel) {
QueryTaskFuture<ThreadSafeMutableSet<Target>> universeValueFuture =
QueryUtil.evalAll(env, context, expression);
Function<ThreadSafeMutableSet<Target>, QueryTaskFuture<Predicate<SkyKey>>>
getTransitiveClosureAsyncFunction =
universeValue -> {
ThreadSafeAggregateAllSkyKeysCallback aggregateAllCallback =
new ThreadSafeAggregateAllSkyKeysCallback(concurrencyLevel);
return env.execute(
() -> {
UnfilteredSkyKeyLabelDTCVisitor visitor =
new UnfilteredSkyKeyLabelDTCVisitor.Factory(
env,
env.createSkyKeyUniquifier(),
processResultsBatchSize,
aggregateAllCallback)
.create();
visitor.visitAndWaitForCompletion(
SkyQueryEnvironment.makeLabelsStrict(universeValue));
return Predicates.in(aggregateAllCallback.getResult());
});
};
return env.transformAsync(universeValueFuture, getTransitiveClosureAsyncFunction);
}
static QueryTaskFuture<Void> getRdepsInUniverseBoundedParallel(
SkyQueryEnvironment env,
QueryExpression expression,
int depth,
Predicate<SkyKey> universe,
QueryExpressionContext<Target> context,
Callback<Target> callback) {
return env.eval(
expression,
context,
ParallelVisitorUtils.createParallelVisitorCallback(
new RdepsBoundedVisitor.Factory(env, depth, universe, callback)));
}
/** Specialized parallel variant of {@link SkyQueryEnvironment#getRBuildFiles}. */
static void getRBuildFilesParallel(
SkyQueryEnvironment env,
Collection<PathFragment> fileIdentifiers,
QueryExpressionContext<Target> context,
Callback<Target> callback) throws QueryException, InterruptedException {
RBuildFilesVisitor visitor =
new RBuildFilesVisitor(
env,
/*visitUniquifier=*/ env.createSkyKeyUniquifier(),
/*resultUniquifier=*/ env.createSkyKeyUniquifier(),
context,
callback);
visitor.visitFileIdentifiersAndWaitForCompletion(env.graph, fileIdentifiers);
}
static QueryTaskFuture<Void> getDepsUnboundedParallel(
SkyQueryEnvironment env,
QueryExpression expression,
QueryExpressionContext<Target> context,
Callback<Target> callback,
boolean depsNeedFiltering,
QueryExpression caller) {
return env.eval(
expression,
context,
ParallelVisitorUtils.createParallelVisitorCallback(
new DepsUnboundedVisitor.Factory(env, callback, depsNeedFiltering, context, caller)));
}
static class DepAndRdep {
@Nullable final SkyKey dep;
final SkyKey rdep;
DepAndRdep(@Nullable SkyKey dep, SkyKey rdep) {
this.dep = dep;
this.rdep = rdep;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DepAndRdep other)) {
return false;
}
return Objects.equals(dep, other.dep) && rdep.equals(other.rdep);
}
@Override
public int hashCode() {
// N.B. - We deliberately use a garbage-free hashCode implementation (rather than e.g.
// Objects#hash). Depending on the structure of the graph being traversed, this method can
// be very hot.
return 31 * Objects.hashCode(dep) + rdep.hashCode();
}
}
static class DepAndRdepAtDepth {
final DepAndRdep depAndRdep;
final int rdepDepth;
DepAndRdepAtDepth(DepAndRdep depAndRdep, int rdepDepth) {
this.depAndRdep = depAndRdep;
this.rdepDepth = rdepDepth;
}
}
/** Thread-safe {@link AggregateAllCallback} backed by a concurrent {@link Set}. */
@ThreadSafe
private static class ThreadSafeAggregateAllSkyKeysCallback
implements AggregateAllCallback<SkyKey, ImmutableSet<SkyKey>> {
private final Set<SkyKey> results;
private ThreadSafeAggregateAllSkyKeysCallback(int concurrencyLevel) {
this.results =
Collections.newSetFromMap(
new ConcurrentHashMap<>(
/*initialCapacity=*/ concurrencyLevel, /*loadFactor=*/ 0.75f));
}
@Override
public void process(Iterable<SkyKey> partialResult)
throws QueryException, InterruptedException {
Iterables.addAll(results, partialResult);
}
@Override
public ImmutableSet<SkyKey> getResult() {
return ImmutableSet.copyOf(results);
}
}
}