Make workers restart on flags that affect their creation/behaviour.
Also refactors the related code to being cleaner.
RELNOTES: None.
PiperOrigin-RevId: 374365649
diff --git a/src/main/java/com/google/devtools/build/lib/worker/BUILD b/src/main/java/com/google/devtools/build/lib/worker/BUILD
index 1080013..1008d0f 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/worker/BUILD
@@ -26,7 +26,6 @@
"//src/main/java/com/google/devtools/build/lib/exec:spawn_runner",
"//src/main/java/com/google/devtools/build/lib/exec:spawn_strategy_registry",
"//src/main/java/com/google/devtools/build/lib/exec/local",
- "//src/main/java/com/google/devtools/build/lib/exec/local:options",
"//src/main/java/com/google/devtools/build/lib/runtime/commands/events",
"//src/main/java/com/google/devtools/build/lib/sandbox",
"//src/main/java/com/google/devtools/build/lib/shell",
diff --git a/src/main/java/com/google/devtools/build/lib/worker/SimpleWorkerPool.java b/src/main/java/com/google/devtools/build/lib/worker/SimpleWorkerPool.java
index 02249e1..4c67418 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/SimpleWorkerPool.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/SimpleWorkerPool.java
@@ -15,6 +15,7 @@
import com.google.common.base.Throwables;
import java.io.IOException;
+import java.util.Objects;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
@@ -28,8 +29,38 @@
@ThreadSafe
final class SimpleWorkerPool extends GenericKeyedObjectPool<WorkerKey, Worker> {
- public SimpleWorkerPool(WorkerFactory factory, GenericKeyedObjectPoolConfig<Worker> config) {
- super(factory, config);
+ public SimpleWorkerPool(WorkerFactory factory, int max) {
+ super(factory, makeConfig(max));
+ }
+
+ static SimpleWorkerPoolConfig makeConfig(int max) {
+ SimpleWorkerPoolConfig config = new SimpleWorkerPoolConfig();
+
+ // It's better to re-use a worker as often as possible and keep it hot, in order to profit
+ // from JIT optimizations as much as possible.
+ config.setLifo(true);
+
+ // Keep a fixed number of workers running per key.
+ config.setMaxIdlePerKey(max);
+ config.setMaxTotalPerKey(max);
+ config.setMinIdlePerKey(max);
+
+ // Don't limit the total number of worker processes, as otherwise the pool might be full of
+ // workers for one WorkerKey and can't accommodate a worker for another WorkerKey.
+ config.setMaxTotal(-1);
+
+ // Wait for a worker to become ready when a thread needs one.
+ config.setBlockWhenExhausted(true);
+
+ // Always test the liveliness of worker processes.
+ config.setTestOnBorrow(true);
+ config.setTestOnCreate(true);
+ config.setTestOnReturn(true);
+
+ // No eviction of idle workers.
+ config.setTimeBetweenEvictionRunsMillis(-1);
+
+ return config;
}
@Override
@@ -51,4 +82,66 @@
throw new RuntimeException("unexpected", t);
}
}
+
+ /**
+ * Our own configuration class for the {@code SimpleWorkerPool} that correctly implements {@code
+ * equals()} and {@code hashCode()}.
+ */
+ static final class SimpleWorkerPoolConfig extends GenericKeyedObjectPoolConfig<Worker> {
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ SimpleWorkerPoolConfig that = (SimpleWorkerPoolConfig) o;
+ return getBlockWhenExhausted() == that.getBlockWhenExhausted()
+ && getFairness() == that.getFairness()
+ && getJmxEnabled() == that.getJmxEnabled()
+ && getLifo() == that.getLifo()
+ && getMaxWaitMillis() == that.getMaxWaitMillis()
+ && getMinEvictableIdleTimeMillis() == that.getMinEvictableIdleTimeMillis()
+ && getNumTestsPerEvictionRun() == that.getNumTestsPerEvictionRun()
+ && getSoftMinEvictableIdleTimeMillis() == that.getSoftMinEvictableIdleTimeMillis()
+ && getTestOnBorrow() == that.getTestOnBorrow()
+ && getTestOnCreate() == that.getTestOnCreate()
+ && getTestOnReturn() == that.getTestOnReturn()
+ && getTestWhileIdle() == that.getTestWhileIdle()
+ && getTimeBetweenEvictionRunsMillis() == that.getTimeBetweenEvictionRunsMillis()
+ && getMaxIdlePerKey() == that.getMaxIdlePerKey()
+ && getMaxTotal() == that.getMaxTotal()
+ && getMaxTotalPerKey() == that.getMaxTotalPerKey()
+ && getMinIdlePerKey() == that.getMinIdlePerKey()
+ && Objects.equals(getEvictionPolicyClassName(), that.getEvictionPolicyClassName())
+ && Objects.equals(getJmxNameBase(), that.getJmxNameBase())
+ && Objects.equals(getJmxNamePrefix(), that.getJmxNamePrefix());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ getBlockWhenExhausted(),
+ getFairness(),
+ getJmxEnabled(),
+ getLifo(),
+ getMaxWaitMillis(),
+ getMinEvictableIdleTimeMillis(),
+ getNumTestsPerEvictionRun(),
+ getSoftMinEvictableIdleTimeMillis(),
+ getTestOnBorrow(),
+ getTestOnCreate(),
+ getTestOnReturn(),
+ getTestWhileIdle(),
+ getTimeBetweenEvictionRunsMillis(),
+ getMaxIdlePerKey(),
+ getMaxTotal(),
+ getMaxTotalPerKey(),
+ getMinIdlePerKey(),
+ getEvictionPolicyClassName(),
+ getJmxNameBase(),
+ getJmxNamePrefix());
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
index 1b3d25a..b6a189e 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerFactory.java
@@ -18,6 +18,7 @@
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.Objects;
import java.util.Optional;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
@@ -35,23 +36,19 @@
// request_id (which is indistinguishable from 0 in proto3).
private static final AtomicInteger pidCounter = new AtomicInteger(1);
- private WorkerOptions workerOptions;
private final Path workerBaseDir;
private Reporter reporter;
+ private final boolean workerSandboxing;
- public WorkerFactory(WorkerOptions workerOptions, Path workerBaseDir) {
- this.workerOptions = workerOptions;
+ public WorkerFactory(Path workerBaseDir, boolean workerSandboxing) {
this.workerBaseDir = workerBaseDir;
+ this.workerSandboxing = workerSandboxing;
}
public void setReporter(Reporter reporter) {
this.reporter = reporter;
}
- public void setOptions(WorkerOptions workerOptions) {
- this.workerOptions = workerOptions;
- }
-
@Override
public Worker create(WorkerKey key) {
int workerId = pidCounter.getAndIncrement();
@@ -60,7 +57,7 @@
workerBaseDir.getRelative(workTypeName + "-" + workerId + "-" + key.getMnemonic() + ".log");
Worker worker;
- boolean sandboxed = workerOptions.workerSandboxing || key.isSpeculative();
+ boolean sandboxed = workerSandboxing || key.isSpeculative();
if (sandboxed) {
Path workDir = getSandboxedWorkerPath(key, workerId);
worker = new SandboxedWorker(key, workerId, workDir, logFile);
@@ -70,7 +67,7 @@
} else {
worker = new SingleplexWorker(key, workerId, key.getExecRoot(), logFile);
}
- if (workerOptions.workerVerbose) {
+ if (reporter != null) {
reporter.handle(
Event.info(
String.format(
@@ -91,9 +88,7 @@
.getRelative(workspaceName);
}
- /**
- * Use the DefaultPooledObject implementation.
- */
+ /** Use the DefaultPooledObject implementation. */
@Override
public PooledObject<Worker> wrap(Worker worker) {
return new DefaultPooledObject<>(worker);
@@ -102,7 +97,7 @@
/** When a worker process is discarded, destroy its process, too. */
@Override
public void destroyObject(WorkerKey key, PooledObject<Worker> p) {
- if (workerOptions.workerVerbose) {
+ if (reporter != null) {
int workerId = p.getObject().getWorkerId();
reporter.handle(
Event.info(
@@ -122,7 +117,7 @@
Worker worker = p.getObject();
Optional<Integer> exitValue = worker.getExitValue();
if (exitValue.isPresent()) {
- if (workerOptions.workerVerbose && worker.diedUnexpectedly()) {
+ if (reporter != null && worker.diedUnexpectedly()) {
String msg =
String.format(
"%s %s (id %d) has unexpectedly died with exit code %d.",
@@ -140,7 +135,7 @@
boolean filesChanged =
!key.getWorkerFilesCombinedHash().equals(worker.getWorkerFilesCombinedHash());
- if (workerOptions.workerVerbose && reporter != null && filesChanged) {
+ if (reporter != null && filesChanged) {
StringBuilder msg = new StringBuilder();
msg.append(
String.format(
@@ -167,4 +162,21 @@
return !filesChanged;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof WorkerFactory)) {
+ return false;
+ }
+ WorkerFactory that = (WorkerFactory) o;
+ return workerSandboxing == that.workerSandboxing && workerBaseDir.equals(that.workerBaseDir);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(workerBaseDir, workerSandboxing);
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
index a34c9bd..4ce5465 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerModule.java
@@ -15,9 +15,9 @@
import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.Subscribe;
import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
import com.google.devtools.build.lib.buildtool.buildevent.BuildStartingEvent;
@@ -33,23 +33,16 @@
import com.google.devtools.build.lib.sandbox.SandboxHelpers;
import com.google.devtools.build.lib.sandbox.SandboxOptions;
import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.worker.WorkerOptions.MultiResourceConverter;
+import com.google.devtools.build.lib.worker.WorkerPool.WorkerPoolConfig;
import com.google.devtools.common.options.OptionsBase;
import java.io.IOException;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import javax.annotation.Nonnull;
/** A module that adds the WorkerActionContextProvider to the available action context providers. */
public class WorkerModule extends BlazeModule {
private CommandEnvironment env;
private WorkerFactory workerFactory;
- private WorkerPool workerPool;
- private WorkerOptions options;
- private ImmutableMap<String, Integer> workerPoolConfig;
- private ImmutableMap<String, Integer> multiplexPoolConfig;
+ @VisibleForTesting WorkerPool workerPool;
@Override
public Iterable<Class<? extends OptionsBase>> getCommandOptions(Command command) {
@@ -68,21 +61,31 @@
@Subscribe
public void cleanStarting(CleanStartingEvent event) {
if (workerPool != null) {
- this.options = event.getOptionsProvider().getOptions(WorkerOptions.class);
- workerFactory.setReporter(env.getReporter());
- workerFactory.setOptions(options);
+ WorkerOptions options = event.getOptionsProvider().getOptions(WorkerOptions.class);
+ workerFactory.setReporter(options.workerVerbose ? env.getReporter() : null);
shutdownPool(
- "Clean command is running, shutting down worker pool...", /* alwaysLog= */ false);
+ "Clean command is running, shutting down worker pool...",
+ /* alwaysLog= */ false,
+ options.workerVerbose);
}
}
+ /**
+ * Handles updating worker factories and pools when a build starts. If either the workerDir or the
+ * sandboxing flag has changed, we need to recreate the factory, and we clear out logs at the same
+ * time. If options affecting the pools have changed, we just change the pools.
+ */
@Subscribe
public void buildStarting(BuildStartingEvent event) {
- options = event.getRequest().getOptions(WorkerOptions.class);
+ WorkerOptions options = event.getRequest().getOptions(WorkerOptions.class);
+ if (workerFactory != null) {
+ workerFactory.setReporter(options.workerVerbose ? env.getReporter() : null);
+ }
+ Path workerDir =
+ env.getOutputBase().getRelative(env.getRuntime().getProductName() + "-workers");
- if (workerFactory == null) {
- Path workerDir =
- env.getOutputBase().getRelative(env.getRuntime().getProductName() + "-workers");
+ WorkerFactory newWorkerFactory = new WorkerFactory(workerDir, options.workerSandboxing);
+ if (!newWorkerFactory.equals(workerFactory)) {
try {
if (!workerDir.createDirectory()) {
// Clean out old log files.
@@ -102,59 +105,40 @@
.handle(Event.error("Could not create base directory for workers: " + workerDir));
}
- workerFactory = new WorkerFactory(options, workerDir);
+ shutdownPool(
+ "Worker factory configuration has changed, restarting worker pool...",
+ /* alwaysLog= */ true,
+ options.workerVerbose);
+ workerFactory = newWorkerFactory;
+ workerFactory.setReporter(options.workerVerbose ? env.getReporter() : null);
}
- workerFactory.setReporter(env.getReporter());
- workerFactory.setOptions(options);
-
- ImmutableMap<String, Integer> newConfig = createConfigFromOptions(options.workerMaxInstances);
- ImmutableMap<String, Integer> newMultiplexConfig =
- createConfigFromOptions(options.workerMaxMultiplexInstances);
+ WorkerPoolConfig newConfig =
+ new WorkerPoolConfig(
+ workerFactory,
+ options.workerMaxInstances,
+ options.workerMaxMultiplexInstances,
+ options.highPriorityWorkers);
// If the config changed compared to the last run, we have to create a new pool.
- if ((workerPoolConfig != null && !workerPoolConfig.equals(newConfig))
- || (multiplexPoolConfig != null && !multiplexPoolConfig.equals(newMultiplexConfig))) {
+ if (workerPool == null || !newConfig.equals(workerPool.getWorkerPoolConfig())) {
shutdownPool(
- "Worker configuration has changed, restarting worker pool...", /* alwaysLog= */ true);
+ "Worker pool configuration has changed, restarting worker pool...",
+ /* alwaysLog= */ true,
+ options.workerVerbose);
}
if (workerPool == null) {
- workerPoolConfig = newConfig;
- multiplexPoolConfig = newMultiplexConfig;
- workerPool =
- new WorkerPool(
- workerFactory, workerPoolConfig, multiplexPoolConfig, options.highPriorityWorkers);
+ workerPool = new WorkerPool(newConfig);
}
}
- /**
- * Creates a configuration for a worker pool from the options given. If the same mnemonic occurs
- * more than once in the options, the last value passed wins.
- */
- @Nonnull
- private static ImmutableMap<String, Integer> createConfigFromOptions(
- List<Map.Entry<String, Integer>> options) {
- LinkedHashMap<String, Integer> newConfigBuilder = new LinkedHashMap<>();
- for (Map.Entry<String, Integer> entry : options) {
- newConfigBuilder.put(entry.getKey(), entry.getValue());
- }
-
- if (!newConfigBuilder.containsKey("")) {
- // Empty string gives the number of workers for any type of worker not explicitly specified.
- // If no value is given, use the default, 2.
- newConfigBuilder.put("", MultiResourceConverter.DEFAULT_VALUE);
- }
-
- return ImmutableMap.copyOf(newConfigBuilder);
- }
-
@Override
public void registerSpawnStrategies(
SpawnStrategyRegistry.Builder registryBuilder, CommandEnvironment env) {
checkNotNull(workerPool);
SandboxOptions sandboxOptions = env.getOptions().getOptions(SandboxOptions.class);
- options = env.getOptions().getOptions(WorkerOptions.class);
+ WorkerOptions options = env.getOptions().getOptions(WorkerOptions.class);
LocalEnvProvider localEnvProvider = LocalEnvProvider.forCurrentOs(env.getClientEnv());
WorkerSpawnRunner spawnRunner =
new WorkerSpawnRunner(
@@ -178,17 +162,21 @@
@Subscribe
public void buildComplete(BuildCompleteEvent event) {
+ WorkerOptions options = env.getOptions().getOptions(WorkerOptions.class);
if (options != null && options.workerQuitAfterBuild) {
- shutdownPool("Build completed, shutting down worker pool...", /* alwaysLog= */ false);
+ shutdownPool(
+ "Build completed, shutting down worker pool...",
+ /* alwaysLog= */ false,
+ options.workerVerbose);
}
}
/** Shuts down the worker pool and sets {#code workerPool} to null. */
- private void shutdownPool(String reason, boolean alwaysLog) {
+ private void shutdownPool(String reason, boolean alwaysLog, boolean workerVerbose) {
Preconditions.checkArgument(!reason.isEmpty());
if (workerPool != null) {
- if ((options != null && options.workerVerbose) || alwaysLog) {
+ if (workerVerbose || alwaysLog) {
env.getReporter().handle(Event.info(reason));
}
workerPool.close();
@@ -199,7 +187,6 @@
@Override
public void afterCommand() {
this.env = null;
- this.options = null;
if (this.workerFactory != null) {
this.workerFactory.setReporter(null);
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerPool.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerPool.java
index ba630a5..7bb8f64 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerPool.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerPool.java
@@ -16,9 +16,15 @@
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.worker.WorkerOptions.MultiResourceConverter;
import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
@@ -45,70 +51,66 @@
private final AtomicInteger highPriorityWorkersInUse = new AtomicInteger(0);
/** Which mnemonics create high-priority workers. */
private final ImmutableSet<String> highPriorityWorkerMnemonics;
+
+ private final WorkerPoolConfig workerPoolConfig;
/** Map of singleplex worker pools, one per mnemonic. */
private final ImmutableMap<String, SimpleWorkerPool> workerPools;
/** Map of multiplex worker pools, one per mnemonic. */
private final ImmutableMap<String, SimpleWorkerPool> multiplexPools;
+ public WorkerPool(WorkerPoolConfig workerPoolConfig) {
+ this.workerPoolConfig = workerPoolConfig;
+
+ highPriorityWorkerMnemonics =
+ ImmutableSet.copyOf((Iterable<String>) workerPoolConfig.getHighPriorityWorkers());
+
+ Map<String, Integer> config = createConfigFromOptions(workerPoolConfig.getWorkerMaxInstances());
+ Map<String, Integer> multiplexConfig =
+ createConfigFromOptions(workerPoolConfig.getWorkerMaxMultiplexInstances());
+
+ workerPools =
+ createWorkerPools(workerPoolConfig.getWorkerFactory(), config, DEFAULT_MAX_WORKERS);
+ multiplexPools =
+ createWorkerPools(
+ workerPoolConfig.getWorkerFactory(), multiplexConfig, DEFAULT_MAX_MULTIPLEX_WORKERS);
+ }
+
+ public WorkerPoolConfig getWorkerPoolConfig() {
+ return workerPoolConfig;
+ }
+
/**
- * @param factory worker factory
- * @param config pool configuration; max number of workers per WorkerKey for each mnemonic; the
- * empty string key specifies the default maximum
- * @param multiplexConfig like {@code config}, but for multiplex workers
- * @param highPriorityWorkers mnemonics of high priority workers
+ * Creates a configuration for a worker pool from the options given. If the same mnemonic occurs
+ * more than once in the options, the last value passed wins.
*/
- public WorkerPool(
- WorkerFactory factory,
- Map<String, Integer> config,
- Map<String, Integer> multiplexConfig,
- Iterable<String> highPriorityWorkers) {
- highPriorityWorkerMnemonics = ImmutableSet.copyOf(highPriorityWorkers);
- workerPools = createWorkerPools(factory, config, DEFAULT_MAX_WORKERS);
- multiplexPools = createWorkerPools(factory, multiplexConfig, DEFAULT_MAX_MULTIPLEX_WORKERS);
+ @Nonnull
+ private static ImmutableMap<String, Integer> createConfigFromOptions(
+ List<Entry<String, Integer>> options) {
+ LinkedHashMap<String, Integer> newConfigBuilder = new LinkedHashMap<>();
+ for (Map.Entry<String, Integer> entry : options) {
+ newConfigBuilder.put(entry.getKey(), entry.getValue());
+ }
+
+ if (!newConfigBuilder.containsKey("")) {
+ // Empty string gives the number of workers for any type of worker not explicitly specified.
+ // If no value is given, use the default, 2.
+ newConfigBuilder.put("", MultiResourceConverter.DEFAULT_VALUE);
+ }
+
+ return ImmutableMap.copyOf(newConfigBuilder);
}
private static ImmutableMap<String, SimpleWorkerPool> createWorkerPools(
WorkerFactory factory, Map<String, Integer> config, int defaultMaxWorkers) {
ImmutableMap.Builder<String, SimpleWorkerPool> workerPoolsBuilder = ImmutableMap.builder();
config.forEach(
- (key, value) ->
- workerPoolsBuilder.put(key, new SimpleWorkerPool(factory, makeConfig(value))));
+ (key, value) -> workerPoolsBuilder.put(key, new SimpleWorkerPool(factory, value)));
if (!config.containsKey("")) {
- workerPoolsBuilder.put("", new SimpleWorkerPool(factory, makeConfig(defaultMaxWorkers)));
+ workerPoolsBuilder.put("", new SimpleWorkerPool(factory, defaultMaxWorkers));
}
return workerPoolsBuilder.build();
}
- private static WorkerPoolConfig makeConfig(int max) {
- WorkerPoolConfig config = new WorkerPoolConfig();
-
- // It's better to re-use a worker as often as possible and keep it hot, in order to profit
- // from JIT optimizations as much as possible.
- config.setLifo(true);
-
- // Keep a fixed number of workers running per key.
- config.setMaxIdlePerKey(max);
- config.setMaxTotalPerKey(max);
- config.setMinIdlePerKey(max);
-
- // Don't limit the total number of worker processes, as otherwise the pool might be full of
- // workers for one WorkerKey and can't accommodate a worker for another WorkerKey.
- config.setMaxTotal(-1);
-
- // Wait for a worker to become ready when a thread needs one.
- config.setBlockWhenExhausted(true);
-
- // Always test the liveliness of worker processes.
- config.setTestOnBorrow(true);
- config.setTestOnCreate(true);
- config.setTestOnReturn(true);
-
- // No eviction of idle workers.
- config.setTimeBetweenEvictionRunsMillis(-1);
-
- return config;
- }
-
private SimpleWorkerPool getPool(WorkerKey key) {
if (key.isMultiplex()) {
return multiplexPools.getOrDefault(key.getMnemonic(), multiplexPools.get(""));
@@ -197,4 +199,59 @@
workerPools.values().forEach(GenericKeyedObjectPool::close);
multiplexPools.values().forEach(GenericKeyedObjectPool::close);
}
+
+ static class WorkerPoolConfig {
+ private final WorkerFactory workerFactory;
+ private final List<Entry<String, Integer>> workerMaxInstances;
+ private final List<Entry<String, Integer>> workerMaxMultiplexInstances;
+ private final List<String> highPriorityWorkers;
+
+ WorkerPoolConfig(
+ WorkerFactory workerFactory,
+ List<Entry<String, Integer>> workerMaxInstances,
+ List<Entry<String, Integer>> workerMaxMultiplexInstances,
+ List<String> highPriorityWorkers) {
+ this.workerFactory = workerFactory;
+ this.workerMaxInstances = workerMaxInstances;
+ this.workerMaxMultiplexInstances = workerMaxMultiplexInstances;
+ this.highPriorityWorkers = highPriorityWorkers;
+ }
+
+ public WorkerFactory getWorkerFactory() {
+ return workerFactory;
+ }
+
+ public List<Entry<String, Integer>> getWorkerMaxInstances() {
+ return workerMaxInstances;
+ }
+
+ public List<Entry<String, Integer>> getWorkerMaxMultiplexInstances() {
+ return workerMaxMultiplexInstances;
+ }
+
+ public List<String> getHighPriorityWorkers() {
+ return highPriorityWorkers;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof WorkerPoolConfig)) {
+ return false;
+ }
+ WorkerPoolConfig that = (WorkerPoolConfig) o;
+ return workerFactory.equals(that.workerFactory)
+ && workerMaxInstances.equals(that.workerMaxInstances)
+ && workerMaxMultiplexInstances.equals(that.workerMaxMultiplexInstances)
+ && highPriorityWorkers.equals(that.highPriorityWorkers);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ workerFactory, workerMaxInstances, workerMaxMultiplexInstances, highPriorityWorkers);
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerPoolConfig.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerPoolConfig.java
deleted file mode 100644
index 20d40df..0000000
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerPoolConfig.java
+++ /dev/null
@@ -1,79 +0,0 @@
-// 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.worker;
-
-import java.util.Objects;
-import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
-
-/**
- * Our own configuration class for the {@code WorkerPool} that correctly implements {@code equals()}
- * and {@code hashCode()}.
- */
-final class WorkerPoolConfig extends GenericKeyedObjectPoolConfig<Worker> {
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- WorkerPoolConfig that = (WorkerPoolConfig) o;
- return getBlockWhenExhausted() == that.getBlockWhenExhausted()
- && getFairness() == that.getFairness()
- && getJmxEnabled() == that.getJmxEnabled()
- && getLifo() == that.getLifo()
- && getMaxWaitMillis() == that.getMaxWaitMillis()
- && getMinEvictableIdleTimeMillis() == that.getMinEvictableIdleTimeMillis()
- && getNumTestsPerEvictionRun() == that.getNumTestsPerEvictionRun()
- && getSoftMinEvictableIdleTimeMillis() == that.getSoftMinEvictableIdleTimeMillis()
- && getTestOnBorrow() == that.getTestOnBorrow()
- && getTestOnCreate() == that.getTestOnCreate()
- && getTestOnReturn() == that.getTestOnReturn()
- && getTestWhileIdle() == that.getTestWhileIdle()
- && getTimeBetweenEvictionRunsMillis() == that.getTimeBetweenEvictionRunsMillis()
- && getMaxIdlePerKey() == that.getMaxIdlePerKey()
- && getMaxTotal() == that.getMaxTotal()
- && getMaxTotalPerKey() == that.getMaxTotalPerKey()
- && getMinIdlePerKey() == that.getMinIdlePerKey()
- && Objects.equals(getEvictionPolicyClassName(), that.getEvictionPolicyClassName())
- && Objects.equals(getJmxNameBase(), that.getJmxNameBase())
- && Objects.equals(getJmxNamePrefix(), that.getJmxNamePrefix());
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- getBlockWhenExhausted(),
- getFairness(),
- getJmxEnabled(),
- getLifo(),
- getMaxWaitMillis(),
- getMinEvictableIdleTimeMillis(),
- getNumTestsPerEvictionRun(),
- getSoftMinEvictableIdleTimeMillis(),
- getTestOnBorrow(),
- getTestOnCreate(),
- getTestOnReturn(),
- getTestWhileIdle(),
- getTimeBetweenEvictionRunsMillis(),
- getMaxIdlePerKey(),
- getMaxTotal(),
- getMaxTotalPerKey(),
- getMinIdlePerKey(),
- getEvictionPolicyClassName(),
- getJmxNameBase(),
- getJmxNamePrefix());
- }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/worker/BUILD b/src/test/java/com/google/devtools/build/lib/worker/BUILD
index 88afc7f..83935fc 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/worker/BUILD
@@ -61,14 +61,18 @@
srcs = glob(["*Test.java"]),
deps = [
":testutil",
+ "//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:execution_requirements",
+ "//src/main/java/com/google/devtools/build/lib/analysis:blaze_directories",
+ "//src/main/java/com/google/devtools/build/lib/analysis:server_directories",
"//src/main/java/com/google/devtools/build/lib/clock",
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/exec:spawn_runner",
"//src/main/java/com/google/devtools/build/lib/exec/local",
"//src/main/java/com/google/devtools/build/lib/sandbox",
+ "//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",
"//src/main/java/com/google/devtools/build/lib/util:resource_converter",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
diff --git a/src/test/java/com/google/devtools/build/lib/worker/ExampleWorker.java b/src/test/java/com/google/devtools/build/lib/worker/ExampleWorker.java
index 96e2e86..50439bd 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/ExampleWorker.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/ExampleWorker.java
@@ -86,6 +86,7 @@
if (request == null) {
break;
}
+
currentRequest = request;
inputs.clear();
for (Input input : request.getInputsList()) {
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolConfigTest.java b/src/test/java/com/google/devtools/build/lib/worker/SimpleWorkerPoolConfigTest.java
similarity index 85%
rename from src/test/java/com/google/devtools/build/lib/worker/WorkerPoolConfigTest.java
rename to src/test/java/com/google/devtools/build/lib/worker/SimpleWorkerPoolConfigTest.java
index 27b7aac..a314a9c 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolConfigTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/SimpleWorkerPoolConfigTest.java
@@ -14,20 +14,18 @@
package com.google.devtools.build.lib.worker;
import com.google.common.testing.EqualsTester;
-
+import com.google.devtools.build.lib.worker.SimpleWorkerPool.SimpleWorkerPoolConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-/**
- * Test WorkerPoolConfig.
- */
+/** Test SimpleWorkerPoolConfig. */
@RunWith(JUnit4.class)
-public class WorkerPoolConfigTest {
+public class SimpleWorkerPoolConfigTest {
@Test
public void testEquals() throws Exception {
- WorkerPoolConfig config1a = new WorkerPoolConfig();
+ SimpleWorkerPoolConfig config1a = new SimpleWorkerPoolConfig();
config1a.setLifo(true);
config1a.setMaxIdlePerKey(4);
config1a.setMaxTotalPerKey(4);
@@ -39,7 +37,7 @@
config1a.setTestOnReturn(true);
config1a.setTimeBetweenEvictionRunsMillis(-1);
- WorkerPoolConfig config1b = new WorkerPoolConfig();
+ SimpleWorkerPoolConfig config1b = new SimpleWorkerPoolConfig();
config1b.setLifo(true);
config1b.setMaxIdlePerKey(4);
config1b.setMaxTotalPerKey(4);
@@ -51,7 +49,7 @@
config1b.setTestOnReturn(true);
config1b.setTimeBetweenEvictionRunsMillis(-1);
- WorkerPoolConfig config2a = new WorkerPoolConfig();
+ SimpleWorkerPoolConfig config2a = new SimpleWorkerPoolConfig();
config2a.setLifo(true);
config2a.setMaxIdlePerKey(1);
config2a.setMaxTotalPerKey(1);
@@ -63,7 +61,7 @@
config2a.setTestOnReturn(true);
config2a.setTimeBetweenEvictionRunsMillis(-1);
- WorkerPoolConfig config2b = new WorkerPoolConfig();
+ SimpleWorkerPoolConfig config2b = new SimpleWorkerPoolConfig();
config2b.setLifo(true);
config2b.setMaxIdlePerKey(1);
config2b.setMaxTotalPerKey(1);
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java
index fa37349..81671d0 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerFactoryTest.java
@@ -41,7 +41,8 @@
@Test
public void sandboxedWorkerPathEndsWithWorkspaceName() throws Exception {
Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
- WorkerFactory workerFactory = new WorkerFactory(new WorkerOptions(), workerBaseDir);
+ final WorkerOptions workerOptions = new WorkerOptions();
+ WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, workerOptions.workerSandboxing);
WorkerKey workerKey = createWorkerKey(/* mustBeSandboxed */ true, /* proxied */ false);
Path sandboxedWorkerPath = workerFactory.getSandboxedWorkerPath(workerKey, 1);
@@ -66,7 +67,8 @@
@Test
public void workerCreationTypeCheck() throws Exception {
Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
- WorkerFactory workerFactory = new WorkerFactory(new WorkerOptions(), workerBaseDir);
+ final WorkerOptions workerOptions = new WorkerOptions();
+ WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, workerOptions.workerSandboxing);
WorkerKey sandboxedWorkerKey = createWorkerKey(/* mustBeSandboxed */ true, /* proxied */ false);
Worker sandboxedWorker = workerFactory.create(sandboxedWorkerKey);
assertThat(sandboxedWorker.getClass()).isEqualTo(SandboxedWorker.class);
@@ -88,7 +90,8 @@
@Test
public void testMultiplexWorkersShareLogfiles() throws Exception {
Path workerBaseDir = fs.getPath("/outputbase/bazel-workers");
- WorkerFactory workerFactory = new WorkerFactory(new WorkerOptions(), workerBaseDir);
+ final WorkerOptions workerOptions = new WorkerOptions();
+ WorkerFactory workerFactory = new WorkerFactory(workerBaseDir, workerOptions.workerSandboxing);
WorkerKey workerKey1 = createWorkerKey(/* mustBeSandboxed */ false, /* proxied */ true, "arg1");
Worker proxiedWorker1a = workerFactory.create(workerKey1);
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerModuleTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerModuleTest.java
new file mode 100644
index 0000000..a33d1fb
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerModuleTest.java
@@ -0,0 +1,269 @@
+// Copyright 2021 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.worker;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.lib.actions.ExecutionRequirements.WorkerProtocolFormat.JSON;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.eventbus.EventBus;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.analysis.ServerDirectories;
+import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.buildtool.buildevent.BuildStartingEvent;
+import com.google.devtools.build.lib.clock.BlazeClock;
+import com.google.devtools.build.lib.events.Reporter;
+import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.runtime.BlazeRuntime;
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.util.AbruptExitException;
+import com.google.devtools.build.lib.vfs.DigestHashFunction;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.common.options.OptionsParsingResult;
+import java.io.IOException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Tests for WorkerModule. I bet you didn't see that coming, eh? */
+@RunWith(JUnit4.class)
+public class WorkerModuleTest {
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+ @Mock CommandEnvironment env;
+ @Mock BuildRequest request;
+ @Mock OptionsParsingResult startupOptionsProvider;
+
+ private FileSystem fs;
+ private StoredEventHandler storedEventHandler;
+
+ @Before
+ public void setUp() {
+ fs = new InMemoryFileSystem(BlazeClock.instance(), DigestHashFunction.SHA256);
+ }
+
+ @Test
+ public void buildStarting_createsPools()
+ throws AbruptExitException, IOException, InterruptedException {
+ WorkerModule module = new WorkerModule();
+ WorkerOptions options = WorkerOptions.DEFAULTS;
+ when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+ setupEnvironment("/outputRoot");
+
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).isEmpty();
+ assertThat(fs.getPath("/outputRoot/outputBase/bazel-workers").exists()).isTrue();
+ assertThat(module.workerPool).isNotNull();
+ WorkerKey workerKey = TestUtils.createWorkerKey(JSON, fs);
+ Worker worker = module.workerPool.borrowObject(workerKey);
+ assertThat(worker.workerKey).isEqualTo(workerKey);
+ }
+
+ @Test
+ public void buildStarting_restartsOnSandboxChanges() throws IOException, AbruptExitException {
+ WorkerModule module = new WorkerModule();
+ WorkerOptions options = WorkerOptions.DEFAULTS;
+ when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+ setupEnvironment("/outputRoot");
+
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).isEmpty();
+
+ Path workerDir = fs.getPath("/outputRoot/outputBase/bazel-workers");
+ Path aLog = workerDir.getRelative("f.log");
+ aLog.createSymbolicLink(PathFragment.EMPTY_FRAGMENT);
+ WorkerPool oldPool = module.workerPool;
+ options.workerSandboxing = !options.workerSandboxing;
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).hasSize(1);
+ assertThat(storedEventHandler.getEvents().get(0).getMessage())
+ .contains("Worker factory configuration has changed");
+ assertThat(module.workerPool).isNotSameInstanceAs(oldPool);
+ assertThat(aLog.exists()).isFalse();
+ }
+
+ @Test
+ public void buildStarting_workersDestroyedOnRestart()
+ throws IOException, AbruptExitException, InterruptedException {
+ WorkerModule module = new WorkerModule();
+ WorkerOptions options = WorkerOptions.DEFAULTS;
+ options.workerVerbose = true;
+ when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+ setupEnvironment("/outputRoot");
+
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ WorkerKey workerKey = TestUtils.createWorkerKey(JSON, fs);
+ Worker worker = module.workerPool.borrowObject(workerKey);
+ assertThat(worker.workerKey).isEqualTo(workerKey);
+ assertThat(storedEventHandler.getEvents()).hasSize(1);
+ assertThat(storedEventHandler.getEvents().get(0).getMessage())
+ .contains("Created new sandboxed dummy worker");
+ storedEventHandler.clear();
+
+ Path workerDir = fs.getPath("/outputRoot/outputBase/bazel-workers");
+ Path aLog = workerDir.getRelative("f.log");
+ aLog.createSymbolicLink(PathFragment.EMPTY_FRAGMENT);
+ WorkerPool oldPool = module.workerPool;
+ options.workerSandboxing = !options.workerSandboxing;
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).hasSize(1);
+ assertThat(storedEventHandler.getEvents().get(0).getMessage())
+ .contains("Worker factory configuration has changed");
+ assertThat(module.workerPool).isNotSameInstanceAs(oldPool);
+ assertThat(aLog.exists()).isFalse();
+ }
+
+ @Test
+ public void buildStarting_restartsOnOutputbaseChanges() throws IOException, AbruptExitException {
+ WorkerModule module = new WorkerModule();
+ WorkerOptions options = WorkerOptions.DEFAULTS;
+ when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+ setupEnvironment("/outputRoot");
+
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).isEmpty();
+
+ // Log file from old root, doesn't get cleaned
+ Path workerDir = fs.getPath("/outputRoot/outputBase/bazel-workers");
+ Path oldLog = workerDir.getRelative("f.log");
+ oldLog.createSymbolicLink(PathFragment.EMPTY_FRAGMENT);
+
+ WorkerPool oldPool = module.workerPool;
+ setupEnvironment("/otherRootDir");
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).hasSize(1);
+ assertThat(storedEventHandler.getEvents().get(0).getMessage())
+ .contains("Worker factory configuration has changed");
+ assertThat(module.workerPool).isNotSameInstanceAs(oldPool);
+ assertThat(fs.getPath("/otherRootDir/outputBase/bazel-workers").exists()).isTrue();
+ assertThat(oldLog.exists()).isTrue();
+ }
+
+ @Test
+ public void buildStarting_restartsOnHiPrioChanges() throws IOException, AbruptExitException {
+ WorkerModule module = new WorkerModule();
+ WorkerOptions options = WorkerOptions.DEFAULTS;
+ when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+ setupEnvironment("/outputRoot");
+
+ module.beforeCommand(env);
+ // Check that new pools/factories are made with default options
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).isEmpty();
+
+ // Logs are only cleared on factory reset, not on pool reset, so this file should survive
+ Path workerDir = fs.getPath("/outputRoot/outputBase/bazel-workers");
+ Path oldLog = workerDir.getRelative("f.log");
+ oldLog.createSymbolicLink(PathFragment.EMPTY_FRAGMENT);
+
+ WorkerPool oldPool = module.workerPool;
+ options.highPriorityWorkers = ImmutableList.of("foo");
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).hasSize(1);
+ assertThat(storedEventHandler.getEvents().get(0).getMessage())
+ .contains("Worker pool configuration has changed");
+ assertThat(module.workerPool).isNotSameInstanceAs(oldPool);
+ assertThat(oldLog.exists()).isTrue();
+ }
+
+ @Test
+ public void buildStarting_restartsOnNumMultiplexWorkersChanges()
+ throws IOException, AbruptExitException {
+ WorkerModule module = new WorkerModule();
+ WorkerOptions options = WorkerOptions.DEFAULTS;
+ when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+ setupEnvironment("/outputRoot");
+
+ module.beforeCommand(env);
+ // Check that new pools/factories are made with default options
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).isEmpty();
+
+ WorkerPool oldPool = module.workerPool;
+ options.workerMaxMultiplexInstances = Lists.newArrayList(Maps.immutableEntry("foo", 42));
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).hasSize(1);
+ assertThat(storedEventHandler.getEvents().get(0).getMessage())
+ .contains("Worker pool configuration has changed");
+ assertThat(module.workerPool).isNotSameInstanceAs(oldPool);
+ }
+
+ @Test
+ public void buildStarting_restartsOnNumWorkersChanges() throws IOException, AbruptExitException {
+ WorkerModule module = new WorkerModule();
+ WorkerOptions options = WorkerOptions.DEFAULTS;
+
+ when(request.getOptions(WorkerOptions.class)).thenReturn(options);
+ setupEnvironment("/outputRoot");
+
+ module.beforeCommand(env);
+ // Check that new pools/factories are made with default options
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).isEmpty();
+
+ WorkerPool oldPool = module.workerPool;
+ options.workerMaxInstances = Lists.newArrayList(Maps.immutableEntry("bar", 3));
+ module.beforeCommand(env);
+ module.buildStarting(new BuildStartingEvent(env, request));
+ assertThat(storedEventHandler.getEvents()).hasSize(1);
+ assertThat(storedEventHandler.getEvents().get(0).getMessage())
+ .contains("Worker pool configuration has changed");
+ assertThat(module.workerPool).isNotSameInstanceAs(oldPool);
+ }
+
+ private void setupEnvironment(String rootDir) throws IOException, AbruptExitException {
+ storedEventHandler = new StoredEventHandler();
+ Path root = fs.getPath(rootDir);
+ Path outputBase = root.getRelative("outputBase");
+ outputBase.createDirectoryAndParents();
+ when(env.getOutputBase()).thenReturn(outputBase);
+ Path workspace = fs.getPath("/workspace");
+ when(env.getWorkingDirectory()).thenReturn(workspace);
+ ServerDirectories serverDirectories =
+ new ServerDirectories(
+ root.getRelative("userroot/install"), outputBase, root.getRelative("userroot"));
+ BlazeRuntime blazeRuntime =
+ new BlazeRuntime.Builder()
+ .setProductName("bazel")
+ .setServerDirectories(serverDirectories)
+ .setStartupOptionsProvider(startupOptionsProvider)
+ .build();
+ when(env.getRuntime()).thenReturn(blazeRuntime);
+ when(env.getDirectories())
+ .thenReturn(new BlazeDirectories(serverDirectories, null, null, "blaze"));
+ EventBus eventBus = new EventBus();
+ when(env.getEventBus()).thenReturn(eventBus);
+ when(env.getReporter()).thenReturn(new Reporter(eventBus, storedEventHandler));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java
index 036ca7b..20cb532 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerPoolTest.java
@@ -23,15 +23,17 @@
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.devtools.build.lib.clock.BlazeClock;
import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.build.lib.worker.WorkerPool.WorkerPoolConfig;
import java.io.IOException;
import java.lang.Thread.State;
+import java.util.Map.Entry;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.junit.Before;
import org.junit.Rule;
@@ -60,14 +62,13 @@
public void setUp() throws Exception {
fileSystem = new InMemoryFileSystem(BlazeClock.instance(), DigestHashFunction.SHA256);
doAnswer(
- arg -> {
- return new DefaultPooledObject<>(
- new TestWorker(
- arg.getArgument(0),
- workerIds++,
- fileSystem.getPath("/workDir"),
- fileSystem.getPath("/logDir")));
- })
+ arg ->
+ new DefaultPooledObject<>(
+ new TestWorker(
+ arg.getArgument(0),
+ workerIds++,
+ fileSystem.getPath("/workDir"),
+ fileSystem.getPath("/logDir"))))
.when(factoryMock)
.makeObject(any());
when(factoryMock.validateObject(any(), any())).thenReturn(true);
@@ -77,10 +78,8 @@
public void testBorrow_createsWhenNeeded() throws Exception {
WorkerPool workerPool =
new WorkerPool(
- factoryMock,
- ImmutableMap.of("mnem", 2, "", 1),
- ImmutableMap.of(),
- Lists.newArrayList());
+ new WorkerPoolConfig(
+ factoryMock, entryList("mnem", 2, "", 1), entryList(), Lists.newArrayList()));
WorkerKey workerKey = createWorkerKey(fileSystem, "mnem", false);
Worker worker1 = workerPool.borrowObject(workerKey);
Worker worker2 = workerPool.borrowObject(workerKey);
@@ -93,10 +92,8 @@
public void testBorrow_reusesWhenPossible() throws Exception {
WorkerPool workerPool =
new WorkerPool(
- factoryMock,
- ImmutableMap.of("mnem", 2, "", 1),
- ImmutableMap.of(),
- Lists.newArrayList());
+ new WorkerPoolConfig(
+ factoryMock, entryList("mnem", 2, "", 1), entryList(), Lists.newArrayList()));
WorkerKey workerKey = createWorkerKey(fileSystem, "mnem", false);
Worker worker1 = workerPool.borrowObject(workerKey);
workerPool.returnObject(workerKey, worker1);
@@ -109,10 +106,8 @@
public void testBorrow_usesDefault() throws Exception {
WorkerPool workerPool =
new WorkerPool(
- factoryMock,
- ImmutableMap.of("mnem", 2, "", 1),
- ImmutableMap.of(),
- Lists.newArrayList());
+ new WorkerPoolConfig(
+ factoryMock, entryList("mnem", 2, "", 1), entryList(), Lists.newArrayList()));
WorkerKey workerKey1 = createWorkerKey(fileSystem, "mnem", false);
Worker worker1 = workerPool.borrowObject(workerKey1);
Worker worker1a = workerPool.borrowObject(workerKey1);
@@ -129,10 +124,8 @@
public void testBorrow_pooledByKey() throws Exception {
WorkerPool workerPool =
new WorkerPool(
- factoryMock,
- ImmutableMap.of("mnem", 2, "", 1),
- ImmutableMap.of(),
- Lists.newArrayList());
+ new WorkerPoolConfig(
+ factoryMock, entryList("mnem", 2, "", 1), entryList(), Lists.newArrayList()));
WorkerKey workerKey1 = createWorkerKey(fileSystem, "mnem", false);
Worker worker1 = workerPool.borrowObject(workerKey1);
Worker worker1a = workerPool.borrowObject(workerKey1);
@@ -149,10 +142,11 @@
public void testBorrow_separateMultiplexWorkers() throws Exception {
WorkerPool workerPool =
new WorkerPool(
- factoryMock,
- ImmutableMap.of("mnem", 1, "", 1),
- ImmutableMap.of("mnem", 2, "", 1),
- Lists.newArrayList());
+ new WorkerPoolConfig(
+ factoryMock,
+ entryList("mnem", 1, "", 1),
+ entryList("mnem", 2, "", 1),
+ Lists.newArrayList()));
WorkerKey workerKey = createWorkerKey(fileSystem, "mnem", false);
Worker worker1 = workerPool.borrowObject(workerKey);
assertThat(worker1.getWorkerId()).isEqualTo(1);
@@ -175,10 +169,11 @@
public void testBorrow_allowsOneHiPrio() throws Exception {
WorkerPool workerPool =
new WorkerPool(
- factoryMock,
- ImmutableMap.of("loprio", 2, "hiprio", 2, "", 1),
- ImmutableMap.of(),
- ImmutableList.of("hiprio"));
+ new WorkerPoolConfig(
+ factoryMock,
+ entryList("loprio", 2, "hiprio", 2, "", 1),
+ entryList(),
+ ImmutableList.of("hiprio")));
WorkerKey workerKey1 = createWorkerKey(fileSystem, "hiprio", false);
Worker worker1 = workerPool.borrowObject(workerKey1);
assertThat(worker1.getWorkerId()).isEqualTo(1);
@@ -194,10 +189,11 @@
public void testBorrow_twoHiPrioBlocks() throws Exception {
WorkerPool workerPool =
new WorkerPool(
- factoryMock,
- ImmutableMap.of("loprio", 2, "hiprio", 2, "", 1),
- ImmutableMap.of(),
- ImmutableList.of("hiprio"));
+ new WorkerPoolConfig(
+ factoryMock,
+ entryList("loprio", 2, "hiprio", 2, "", 1),
+ entryList(),
+ ImmutableList.of("hiprio")));
WorkerKey workerKey1 = createWorkerKey(fileSystem, "hiprio", false);
Worker worker1 = workerPool.borrowObject(workerKey1);
Worker worker1a = workerPool.borrowObject(workerKey1);
@@ -238,4 +234,21 @@
verify(factoryMock, times(2)).makeObject(workerKey1);
verify(factoryMock, times(1)).makeObject(workerKey2);
}
+
+ private static ImmutableList<Entry<String, Integer>> entryList() {
+ return ImmutableList.of();
+ }
+
+ private static ImmutableList<Entry<String, Integer>> entryList(
+ String key1, int value1, String key2, int value2) {
+ return ImmutableList.of(Maps.immutableEntry(key1, value1), Maps.immutableEntry(key2, value2));
+ }
+
+ private static ImmutableList<Entry<String, Integer>> entryList(
+ String key1, int value1, String key2, int value2, String key3, int value3) {
+ return ImmutableList.of(
+ Maps.immutableEntry(key1, value1),
+ Maps.immutableEntry(key2, value2),
+ Maps.immutableEntry(key3, value3));
+ }
}
diff --git a/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
index fcdfea1..dbe71a4 100644
--- a/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/worker/WorkerSpawnRunnerTest.java
@@ -47,6 +47,7 @@
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.build.lib.worker.WorkerPool.WorkerPoolConfig;
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest;
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse;
import java.io.IOException;
@@ -85,20 +86,21 @@
private WorkerPool createWorkerPool() {
return new WorkerPool(
- new WorkerFactory(options, fs.getPath("/workerBase")) {
- @Override
- public Worker create(WorkerKey key) {
- return worker;
- }
+ new WorkerPoolConfig(
+ new WorkerFactory(fs.getPath("/workerBase"), options.workerSandboxing) {
+ @Override
+ public Worker create(WorkerKey key) {
+ return worker;
+ }
- @Override
- public boolean validateObject(WorkerKey key, PooledObject<Worker> p) {
- return true;
- }
- },
- ImmutableMap.of(),
- ImmutableMap.of(),
- ImmutableList.of());
+ @Override
+ public boolean validateObject(WorkerKey key, PooledObject<Worker> p) {
+ return true;
+ }
+ },
+ ImmutableList.of(),
+ ImmutableList.of(),
+ ImmutableList.of()));
}
@Test