blob: ae09ebb429639114c663e56aa150cb2d2ed8fb1f [file] [log] [blame]
// Copyright 2018 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.dynamic;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
import com.google.devtools.build.lib.actions.ExecutorInitException;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnActionContext;
import com.google.devtools.build.lib.actions.Spawns;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.concurrent.ExecutorUtil;
import com.google.devtools.build.lib.exec.ExecutionPolicy;
import com.google.devtools.build.lib.exec.ExecutorBuilder;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.common.options.OptionsBase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
/**
* {@link BlazeModule} providing support for dynamic spawn execution and scheduling.
*/
public class DynamicExecutionModule extends BlazeModule {
private ExecutorService executorService;
private static final Logger logger = Logger.getLogger(DynamicExecutionModule.class.getName());
static List<Map.Entry<String, List<String>>> localStrategiesByMnemonic;
static List<Map.Entry<String, List<String>>> remoteStrategiesByMnemonic;
@Override
public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) {
return "build".equals(command.name())
? ImmutableList.<Class<? extends OptionsBase>>of(DynamicExecutionOptions.class)
: ImmutableList.<Class<? extends OptionsBase>>of();
}
@Override
public void beforeCommand(CommandEnvironment env) {
executorService =
Executors.newCachedThreadPool(
new ThreadFactoryBuilder().setNameFormat("dynamic-execution-thread-%d").build());
env.getEventBus().register(this);
}
/**
* Adds a strategy that backs the dynamic scheduler to the executor builder.
*
* @param builder the executor builder to modify
* @param name the name of the strategy
* @param flagName name of the flag the strategy came from; used for error reporting
* purposes only
* @throws ExecutorInitException if the provided strategy would cause a scheduling cycle
*/
private static void addBackingStrategy(ExecutorBuilder builder, String name, String flagName)
throws ExecutorInitException {
ExecutionStrategy strategy = DynamicSpawnStrategy.class.getAnnotation(ExecutionStrategy.class);
checkNotNull(strategy, "DynamicSpawnStrategy lacks expected ExecutionStrategy annotation");
if (Arrays.asList(strategy.name()).contains(name)) {
throw new ExecutorInitException("Cannot use strategy " + name + " in flag " + flagName
+ " as it would create a cycle during execution");
}
builder.addStrategyByContext(SpawnActionContext.class, name);
}
private static void addStrategiesByMnemonic(
List<Map.Entry<String, List<String>>> strategies, ExecutorBuilder builder, String flagName)
throws ExecutorInitException {
List<String> mnemonics = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : strategies) {
if (mnemonics.contains(entry.getKey())) {
logger.warning(
String.format(
"Strategy for mnemonic %s set twice. Using most recent value (%s)",
entry.getKey(), entry.getValue()));
}
mnemonics.add(entry.getKey());
for (String strategy : entry.getValue()) {
addBackingStrategy(builder, strategy, flagName);
}
}
}
@VisibleForTesting
static void setDefaultStrategiesByMnemonic(DynamicExecutionOptions options) {
// Options that set "allowMultiple" to true ignore the default value, so we replicate that
// functionality here. Additionally, since we are still supporting --dynamic_worker_strategy,
// but will deprecate it soon, we add its functionality to --dynamic_local_strategy. This allows
// users to set --dynamic_local_strategy and not --dynamic_worker_strategy to stop defaulting to
// worker strategy.
// TODO(steinman): Deprecate --dynamic_worker_strategy and clean this up.
if (options.dynamicLocalStrategy == null || options.dynamicLocalStrategy.isEmpty()) {
localStrategiesByMnemonic =
options.dynamicWorkerStrategy.isEmpty()
? ImmutableList.of(Maps.immutableEntry("", ImmutableList.of("worker", "sandboxed")))
: ImmutableList.of(
Maps.immutableEntry(
"", ImmutableList.of(options.dynamicWorkerStrategy, "sandboxed")));
} else {
localStrategiesByMnemonic = options.dynamicLocalStrategy;
if (!options.dynamicWorkerStrategy.isEmpty()) {
for (int i = 0; i < localStrategiesByMnemonic.size(); i++) {
if ("".equals(localStrategiesByMnemonic.get(i).getKey())) {
List<String> newValue = Lists.newArrayList(options.dynamicWorkerStrategy);
newValue.addAll(localStrategiesByMnemonic.get(i).getValue());
localStrategiesByMnemonic.set(i, Maps.immutableEntry("", newValue));
break;
}
}
}
}
remoteStrategiesByMnemonic =
(options.dynamicRemoteStrategy == null || options.dynamicRemoteStrategy.isEmpty())
? ImmutableList.of(Maps.immutableEntry("", ImmutableList.of("remote")))
: options.dynamicRemoteStrategy;
}
@Override
public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorBuilder builder)
throws ExecutorInitException {
DynamicExecutionOptions options = env.getOptions().getOptions(DynamicExecutionOptions.class);
if (options.internalSpawnScheduler) {
if (options.legacySpawnScheduler) {
builder.addActionContext(
new LegacyDynamicSpawnStrategy(executorService, options, this::getExecutionPolicy));
} else {
builder.addActionContext(
new DynamicSpawnStrategy(executorService, options, this::getExecutionPolicy));
}
builder.addStrategyByContext(SpawnActionContext.class, "dynamic");
setDefaultStrategiesByMnemonic(options);
addStrategiesByMnemonic(remoteStrategiesByMnemonic, builder, "--dynamic_remote_strategy");
addStrategiesByMnemonic(localStrategiesByMnemonic, builder, "--dynamic_local_strategy");
}
}
/**
* Use the {@link Spawn} metadata to determine if it can be executed locally, remotely, or both.
* @param spawn the {@link Spawn} action
* @return the {@link ExecutionPolicy} containing local/remote execution policies
*/
protected ExecutionPolicy getExecutionPolicy(Spawn spawn) {
if (!Spawns.mayBeExecutedRemotely(spawn)) {
return ExecutionPolicy.LOCAL_EXECUTION_ONLY;
}
return ExecutionPolicy.ANYWHERE;
}
@Override
public void afterCommand() {
ExecutorUtil.interruptibleShutdown(executorService);
executorService = null;
}
}