Experimental flag to skip dynamic execution until there has been a successful build. This allows doing a first clean build in situations where dynamic execution interferes too much.
PiperOrigin-RevId: 403955415
diff --git a/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModule.java b/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModule.java
index 3f876d9..db9b775 100644
--- a/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModule.java
+++ b/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModule.java
@@ -19,11 +19,16 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import com.google.common.eventbus.Subscribe;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnStrategy;
import com.google.devtools.build.lib.actions.Spawns;
+import com.google.devtools.build.lib.buildtool.BuildResult;
+import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
import com.google.devtools.build.lib.concurrent.ExecutorUtil;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.exec.ExecutionPolicy;
import com.google.devtools.build.lib.exec.SpawnStrategyRegistry;
import com.google.devtools.build.lib.runtime.BlazeModule;
@@ -42,13 +47,18 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-/**
- * {@link BlazeModule} providing support for dynamic spawn execution and scheduling.
- */
+/** {@link BlazeModule} providing support for dynamic spawn execution and scheduling. */
public class DynamicExecutionModule extends BlazeModule {
private ExecutorService executorService;
+ /**
+ * If true, this is the first build since this server started (excluding failed builds). This
+ * allows turning off dynamic execution for an initial build, avoiding dynamic execution on most
+ * clean builds.
+ */
+ private static boolean firstBuild = true;
+
public DynamicExecutionModule() {}
@VisibleForTesting
@@ -125,13 +135,17 @@
SpawnStrategyRegistry.Builder registryBuilder, CommandEnvironment env)
throws AbruptExitException {
registerSpawnStrategies(
- registryBuilder, env.getOptions().getOptions(DynamicExecutionOptions.class));
+ registryBuilder,
+ env.getOptions().getOptions(DynamicExecutionOptions.class),
+ env.getReporter());
}
// CommandEnvironment is difficult to access in tests, so use this method for testing.
@VisibleForTesting
final void registerSpawnStrategies(
- SpawnStrategyRegistry.Builder registryBuilder, DynamicExecutionOptions options)
+ SpawnStrategyRegistry.Builder registryBuilder,
+ DynamicExecutionOptions options,
+ Reporter reporter)
throws AbruptExitException {
if (!options.internalSpawnScheduler) {
return;
@@ -144,8 +158,12 @@
this::getExecutionPolicy,
this::getPostProcessingSpawnForLocalExecution);
registryBuilder.registerStrategy(strategy, "dynamic", "dynamic_worker");
-
- registryBuilder.addDynamicLocalStrategies(getLocalStrategies(options));
+ if (firstBuild && options.skipFirstBuild) {
+ reporter.handle(
+ Event.info("Disabling dynamic execution until we have seen a successful build"));
+ } else {
+ registryBuilder.addDynamicLocalStrategies(getLocalStrategies(options));
+ }
registryBuilder.addDynamicRemoteStrategies(getRemoteStrategies(options));
}
@@ -196,6 +214,14 @@
return Optional.empty();
}
+ @Subscribe
+ public void buildCompleteEvent(BuildCompleteEvent event) {
+ BuildResult result = event.getResult();
+ if (result.getSuccess()) {
+ firstBuild = false;
+ }
+ }
+
@Override
public void afterCommand() {
ExecutorUtil.interruptibleShutdown(executorService);
diff --git a/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionOptions.java b/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionOptions.java
index 19d844e..2a11c13 100644
--- a/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/dynamic/DynamicExecutionOptions.java
@@ -22,9 +22,7 @@
import java.util.List;
import java.util.Map;
-/**
- * Options related to dynamic spawn execution.
- */
+/** Options related to dynamic spawn execution. */
public class DynamicExecutionOptions extends OptionsBase {
@Option(
@@ -91,22 +89,20 @@
public String dynamicWorkerStrategy;
@Option(
- name = "experimental_local_execution_delay",
- documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
- effectTags = {OptionEffectTag.UNKNOWN},
- defaultValue = "1000",
- help =
- "How many milliseconds should local execution be delayed, if remote execution was faster"
- + " during a build at least once?"
- )
+ name = "experimental_local_execution_delay",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ defaultValue = "1000",
+ help =
+ "How many milliseconds should local execution be delayed, if remote execution was faster"
+ + " during a build at least once?")
public int localExecutionDelay;
@Option(
- name = "experimental_debug_spawn_scheduler",
- documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
- effectTags = {OptionEffectTag.UNKNOWN},
- defaultValue = "false"
- )
+ name = "experimental_debug_spawn_scheduler",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ defaultValue = "false")
public boolean debugSpawnScheduler;
@Option(
@@ -130,4 +126,14 @@
+ "execution info if --experimental_require_availability_info=true. No-op if "
+ "--experimental_require_availability_info=false.")
public List<String> availabilityInfoExempt;
+
+ @Option(
+ name = "experimental_dynamic_skip_first_build",
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ defaultValue = "false",
+ help =
+ "If set, dynamic execution is turned off until there has been at least one successful"
+ + " build.")
+ public boolean skipFirstBuild;
}
diff --git a/src/test/java/com/google/devtools/build/lib/dynamic/BUILD b/src/test/java/com/google/devtools/build/lib/dynamic/BUILD
index 471587f..494192b 100644
--- a/src/test/java/com/google/devtools/build/lib/dynamic/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/dynamic/BUILD
@@ -44,6 +44,7 @@
"//src/main/java/com/google/devtools/build/lib/actions:localhost_capacity",
"//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/dynamic",
+ "//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/exec:blaze_executor",
"//src/main/java/com/google/devtools/build/lib/exec:execution_options",
"//src/main/java/com/google/devtools/build/lib/exec:module_action_context_registry",
@@ -71,10 +72,24 @@
size = "small",
srcs = ["DynamicExecutionModuleTest.java"],
deps = [
+ "//src/main/java/com/google/devtools/build/lib:runtime",
+ "//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
+ "//src/main/java/com/google/devtools/build/lib/actions:localhost_capacity",
"//src/main/java/com/google/devtools/build/lib/dynamic",
+ "//src/main/java/com/google/devtools/build/lib/events",
+ "//src/main/java/com/google/devtools/build/lib/exec:spawn_strategy_registry",
"//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",
+ "//src/main/java/com/google/devtools/build/lib/util:detailed_exit_code",
+ "//src/main/java/com/google/devtools/build/lib/util:exit_code",
+ "//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/common/options",
+ "//src/main/protobuf:failure_details_java_proto",
+ "//src/test/java/com/google/devtools/build/lib/actions/util",
+ "//src/test/java/com/google/devtools/build/lib/testutil:TestUtils",
+ "//src/test/java/com/google/devtools/build/lib/vfs/util",
"//third_party:guava",
+ "//third_party:jsr305",
"//third_party:junit4",
"//third_party:truth",
],
diff --git a/src/test/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModuleTest.java b/src/test/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModuleTest.java
index 17a8c19..9ab87bc 100644
--- a/src/test/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/dynamic/DynamicExecutionModuleTest.java
@@ -15,15 +15,47 @@
import static com.google.common.truth.Truth.assertThat;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.actions.ActionContext.ActionContextRegistry;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.ArtifactRoot;
+import com.google.devtools.build.lib.actions.BaseSpawn;
+import com.google.devtools.build.lib.actions.DynamicStrategyRegistry.DynamicMode;
+import com.google.devtools.build.lib.actions.EmptyRunfilesSupplier;
+import com.google.devtools.build.lib.actions.ResourceSet;
+import com.google.devtools.build.lib.actions.SandboxedSpawnStrategy;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.actions.SpawnContinuation;
+import com.google.devtools.build.lib.actions.SpawnResult;
+import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
+import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction;
+import com.google.devtools.build.lib.buildtool.BuildResult;
+import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.exec.SpawnStrategyRegistry;
+import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
+import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.util.DetailedExitCode;
+import com.google.devtools.build.lib.util.ExitCode;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.Root;
+import com.google.devtools.build.lib.vfs.util.FileSystems;
import com.google.devtools.common.options.Converters;
import com.google.devtools.common.options.OptionsParsingException;
+import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executors;
+import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,10 +66,12 @@
public class DynamicExecutionModuleTest {
private DynamicExecutionModule module;
private DynamicExecutionOptions options;
+ private Path testRoot;
@Before
- public void setUp() {
- module = new DynamicExecutionModule();
+ public void setUp() throws IOException {
+ testRoot = TestUtils.createUniqueTmpDir(FileSystems.getNativeFileSystem());
+ module = new DynamicExecutionModule(Executors.newCachedThreadPool());
options = new DynamicExecutionOptions();
options.dynamicWorkerStrategy = ""; // default
options.dynamicLocalStrategy = Collections.emptyList(); // default
@@ -78,6 +112,96 @@
}
@Test
+ public void testRegisterSpawnStrategies_unsetsLocalStrategiesOnFirstBuild()
+ throws AbruptExitException, OptionsParsingException, UserExecException {
+ options.skipFirstBuild = true;
+ options.internalSpawnScheduler = true;
+ options.dynamicLocalStrategy = parseStrategiesToOptions("Foo=local", "local");
+ options.dynamicRemoteStrategy = parseStrategiesToOptions("Foo=remote", "remote");
+ SpawnStrategyRegistry.Builder registryBuilder = new SpawnStrategyRegistry.Builder();
+ registerStrategy(registryBuilder, "local");
+ registerStrategy(registryBuilder, "remote");
+ // "dynamic" strategy will be added by registerSpawnStrategies().
+
+ Reporter reporter = new Reporter(new EventBus());
+ Spawn spawn = newCustomSpawn("Foo", ImmutableMap.of());
+
+ // First build skips dynamic execution
+ module.registerSpawnStrategies(registryBuilder, options, reporter);
+ assertThat(registryBuilder.build().getDynamicSpawnActionContexts(spawn, DynamicMode.LOCAL))
+ .isEmpty();
+
+ // Pretend that the build failed
+ BuildResult buildResult = new BuildResult(1L);
+ buildResult.setDetailedExitCode(
+ DetailedExitCode.of(ExitCode.ANALYSIS_FAILURE, FailureDetail.newBuilder().build()));
+ module.buildCompleteEvent(new BuildCompleteEvent(buildResult));
+
+ // Second build, after no successful builds, skips dynamic execution.
+ module.registerSpawnStrategies(registryBuilder, options, reporter);
+ assertThat(registryBuilder.build().getDynamicSpawnActionContexts(spawn, DynamicMode.LOCAL))
+ .isEmpty();
+
+ // Pretend that the build succeeded
+ buildResult.setDetailedExitCode(DetailedExitCode.success());
+ module.buildCompleteEvent(new BuildCompleteEvent(buildResult));
+
+ // Third build - since the second succeeded, we shouldn't skip any more.
+ module.registerSpawnStrategies(registryBuilder, options, reporter);
+ assertThat(registryBuilder.build().getDynamicSpawnActionContexts(spawn, DynamicMode.LOCAL))
+ .isNotEmpty();
+ }
+
+ private void registerStrategy(SpawnStrategyRegistry.Builder registryBuilder, String name) {
+ registryBuilder.registerStrategy(
+ new SandboxedSpawnStrategy() {
+ @Override
+ public ImmutableList<SpawnResult> exec(
+ Spawn spawn,
+ ActionExecutionContext actionExecutionContext,
+ @Nullable StopConcurrentSpawns stopConcurrentSpawns) {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public ImmutableList<SpawnResult> exec(
+ Spawn spawn, ActionExecutionContext actionExecutionContext) {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public SpawnContinuation beginExecution(
+ Spawn spawn, ActionExecutionContext actionExecutionContext)
+ throws InterruptedException {
+ return SandboxedSpawnStrategy.super.beginExecution(spawn, actionExecutionContext);
+ }
+
+ @Override
+ public boolean canExec(Spawn spawn, ActionContextRegistry actionContextRegistry) {
+ return false;
+ }
+
+ @Override
+ public boolean canExecWithLegacyFallback(
+ Spawn spawn, ActionContextRegistry actionContextRegistry) {
+ return SandboxedSpawnStrategy.super.canExecWithLegacyFallback(
+ spawn, actionContextRegistry);
+ }
+
+ @Override
+ public void usedContext(ActionContextRegistry actionContextRegistry) {
+ SandboxedSpawnStrategy.super.usedContext(actionContextRegistry);
+ }
+
+ @Override
+ public String toString() {
+ return "Test strategy \"" + name + "\"";
+ }
+ },
+ name);
+ }
+
+ @Test
public void testGetLocalStrategies_canMixSpecificsAndGenericOptions()
throws AbruptExitException, OptionsParsingException {
options.dynamicLocalStrategy = parseStrategiesToOptions("Foo=local,worker", "worker");
@@ -102,4 +226,38 @@
}
return result;
}
+
+ /** Constructs a new spawn with a custom mnemonic and execution info. */
+ Spawn newCustomSpawn(String mnemonic, ImmutableMap<String, String> executionInfo) {
+ Artifact inputArtifact =
+ ActionsTestUtil.createArtifact(
+ ArtifactRoot.asSourceRoot(Root.fromPath(testRoot)), "input.txt");
+ Artifact outputArtifact =
+ ActionsTestUtil.createArtifact(
+ ArtifactRoot.asSourceRoot(Root.fromPath(testRoot)), "output.txt");
+
+ ActionExecutionMetadata action =
+ new NullActionWithMnemonic(mnemonic, ImmutableList.of(inputArtifact), outputArtifact);
+ return new BaseSpawn(
+ ImmutableList.of(),
+ ImmutableMap.of(),
+ executionInfo,
+ EmptyRunfilesSupplier.INSTANCE,
+ action,
+ ResourceSet.create(1, 0, 0));
+ }
+
+ private static class NullActionWithMnemonic extends NullAction {
+ private final String mnemonic;
+
+ private NullActionWithMnemonic(String mnemonic, List<Artifact> inputs, Artifact... outputs) {
+ super(inputs, outputs);
+ this.mnemonic = mnemonic;
+ }
+
+ @Override
+ public String getMnemonic() {
+ return mnemonic;
+ }
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/dynamic/DynamicSpawnStrategyTest.java b/src/test/java/com/google/devtools/build/lib/dynamic/DynamicSpawnStrategyTest.java
index 0bae4cf..b6ae3d7 100644
--- a/src/test/java/com/google/devtools/build/lib/dynamic/DynamicSpawnStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/dynamic/DynamicSpawnStrategyTest.java
@@ -23,6 +23,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
+import com.google.common.eventbus.EventBus;
import com.google.common.flogger.GoogleLogger;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.devtools.build.lib.actions.ActionContext;
@@ -46,6 +47,7 @@
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
import com.google.devtools.build.lib.actions.util.ActionsTestUtil.NullAction;
import com.google.devtools.build.lib.bugreport.BugReporter;
+import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.exec.BlazeExecutor;
import com.google.devtools.build.lib.exec.ExecutionOptions;
import com.google.devtools.build.lib.exec.ModuleActionContextRegistry;
@@ -328,7 +330,8 @@
}
DynamicExecutionModule dynamicExecutionModule = new DynamicExecutionModule(executorService);
- dynamicExecutionModule.registerSpawnStrategies(spawnStrategyRegistryBuilder, options);
+ dynamicExecutionModule.registerSpawnStrategies(
+ spawnStrategyRegistryBuilder, options, new Reporter(new EventBus()));
SpawnStrategyRegistry spawnStrategyRegistry = spawnStrategyRegistryBuilder.build();
diff --git a/src/test/shell/integration/execution_strategies_test.sh b/src/test/shell/integration/execution_strategies_test.sh
index abc29bd..8fd2359 100755
--- a/src/test/shell/integration/execution_strategies_test.sh
+++ b/src/test/shell/integration/execution_strategies_test.sh
@@ -93,6 +93,22 @@
expect_not_log "\"FooBar\" = "
}
+function test_dynamic_strategy_skip_first() {
+ # Remote strategy is "worker" to make the test work both internally and externally.
+ # Since nothing is built, the actual strategy doesn't matter.
+ bazel build --internal_spawn_scheduler --spawn_strategy=dynamic \
+ --dynamic_remote_strategy=worker \
+ --dynamic_local_strategy=standalone \
+ --experimental_dynamic_skip_first_build &> $TEST_log || fail
+ expect_log "Disabling dynamic execution"
+
+ bazel build --internal_spawn_scheduler --spawn_strategy=dynamic \
+ --dynamic_remote_strategy=worker \
+ --dynamic_local_strategy=standalone \
+ --experimental_dynamic_skip_first_build &> $TEST_log || fail
+ expect_not_log "Disabling dynamic execution"
+}
+
# Runs a build, waits for the given dir and file to appear, and then kills
# Bazel to check what happens with said files.
function build_and_interrupt() {