From 84deff35b0a8cfe4bd0845b2accbcd849faf5fba Mon Sep 17 00:00:00 2001 From: Alessandro Patti Date: Tue, 8 Aug 2023 12:52:14 +0200 Subject: [PATCH 1/6] Create cpgroups utils --- .../analysis/test/TestTargetProperties.java | 1 + .../devtools/build/lib/sandbox/cgroups/BUILD | 30 +++ .../build/lib/sandbox/cgroups/Controller.java | 44 ++++ .../build/lib/sandbox/cgroups/Hierarchy.java | 56 +++++ .../build/lib/sandbox/cgroups/Mount.java | 53 +++++ .../lib/sandbox/cgroups/VirtualCGroup.java | 211 ++++++++++++++++++ .../lib/sandbox/cgroups/v1/LegacyCpu.java | 33 +++ .../lib/sandbox/cgroups/v1/LegacyMemory.java | 30 +++ .../lib/sandbox/cgroups/v2/UnifiedCpu.java | 32 +++ .../lib/sandbox/cgroups/v2/UnifiedMemory.java | 29 +++ 10 files changed, 519 insertions(+) create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/BUILD create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Hierarchy.java create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Mount.java create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyCpu.java create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyMemory.java create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedCpu.java create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedMemory.java diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java index 90b7a2c4af8f2a..0a78cc351ff566 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java @@ -119,6 +119,7 @@ private static ResourceSet getResourceSetFromSize(TestSize size) { // This will overwrite whatever TargetUtils put there, which might be confusing. executionInfo.putAll(executionRequirements.getExecutionInfo()); } + ruleContext.getConfiguration().modifyExecutionInfo(executionInfo, TestRunnerAction.MNEMONIC); this.executionInfo = ImmutableMap.copyOf(executionInfo); diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/BUILD b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/BUILD new file mode 100644 index 00000000000000..aec4f8b667265c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/BUILD @@ -0,0 +1,30 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//src:__subpackages__"], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), + visibility = ["//src:__subpackages__"], +) + +java_library( + name = "cgroups", + srcs = glob([ + "*.java", + "v1/*.java", + "v2/*.java", + ]), + deps = [ + "//src/main/java/com/google/devtools/build/lib/actions:exec_exception", + "//src/main/java/com/google/devtools/build/lib/events", + "//src/main/protobuf:failure_details_java_proto", + "//third_party:auto_value", + "//third_party:flogger", + "//third_party:guava", + "//third_party:jsr305", + ], +) diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java new file mode 100644 index 00000000000000..23aa9ed2151e87 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java @@ -0,0 +1,44 @@ +package com.google.devtools.build.lib.sandbox.cgroups; + +import com.google.devtools.build.lib.actions.ExecException; +import com.google.devtools.build.lib.server.FailureDetails; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.nio.file.Path; + +public interface Controller { + default boolean isLegacy() throws IOException { + return !getPath().resolve("cgroup.controllers").toFile().exists(); + } + + static T getDefault(Class clazz) { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws ExecException { + throw new ExecException("Cgroup requested by cgroups are not available!") { + protected FailureDetails.FailureDetail getFailureDetail(String message) { + return FailureDetails.FailureDetail.newBuilder().setMessage(message).build(); + } + }; + + } + }; + + return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler); + } + + Path getPath() throws IOException; + + interface Memory extends Controller { + void setMaxBytes(long bytes) throws IOException; + long getMaxBytes() throws IOException; + } + interface Cpu extends Controller { + void setCpus(float cpus) throws IOException; + int getCpus() throws IOException; + } +} \ No newline at end of file diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Hierarchy.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Hierarchy.java new file mode 100644 index 00000000000000..9db168ac410543 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Hierarchy.java @@ -0,0 +1,56 @@ +package com.google.devtools.build.lib.sandbox.cgroups; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.io.Files; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@AutoValue +public abstract class Hierarchy { + public abstract Integer id(); + public abstract List controllers(); + public abstract Path path(); + public boolean isV2() { + return controllers().isEmpty() && id() == 0; + } + + /** + * A regexp that matches entries in {@code /proc/self/cgroup}. + * + * The format is documented in https://man7.org/linux/man-pages/man7/cgroups.7.html + */ + private static final Pattern PROC_CGROUPS_PATTERN = + Pattern.compile("^(?\\d+):(?[^:]*):(?.+)"); + + static Hierarchy create(Integer id, List controllers, String path) { + return new AutoValue_Hierarchy(id, controllers, Paths.get(path)); + } + + static List parse(File procCgroup) throws IOException { + ImmutableList.Builder hierarchies = ImmutableList.builder(); + for (String line : Files.readLines(procCgroup, StandardCharsets.UTF_8)) { + Matcher m = PROC_CGROUPS_PATTERN.matcher(line); + if (!m.matches()) { + continue; + } + + Integer id = Integer.parseInt(m.group("id")); + String path = m.group("file"); + List cs = ImmutableList.copyOf(m.group("controllers").split(",")); + hierarchies.add(Hierarchy.create(id, cs, path)); + } + return hierarchies.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Mount.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Mount.java new file mode 100644 index 00000000000000..4e86e1ec235de0 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Mount.java @@ -0,0 +1,53 @@ +package com.google.devtools.build.lib.sandbox.cgroups; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.io.Files; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@AutoValue +public abstract class Mount { + /** + * A regexp that matches cgroups entries in {@code /proc/mounts}. + * + * The format is documented in https://man7.org/linux/man-pages/man5/fstab.5.html + */ + private static final Pattern CGROUPS_MOUNT_PATTERN = + Pattern.compile("^[^\\s#]\\S*\\s+(?\\S*)\\s+(?cgroup2?)\\s+(?\\S*).*"); + + public abstract Path path(); + public abstract String type(); + public abstract List opts(); + public boolean isV2() { + return type().equals("cgroup2"); + } + + static Mount create(String path, String type, List opts) { + return new AutoValue_Mount(Paths.get(path), type, opts); + } + + static List parse(File procMounts) throws IOException { + ImmutableList.Builder mounts = ImmutableList.builder(); + + for (String mount: Files.readLines(procMounts, StandardCharsets.UTF_8)) { + Matcher m = CGROUPS_MOUNT_PATTERN.matcher(mount); + if (!m.matches()) { + continue; + } + + String path = m.group("file"); + String type = m.group("vfstype"); + List opts = ImmutableList.copyOf(m.group("mntops").split(",")); + mounts.add(Mount.create(path, type, opts)); + } + return mounts.build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java new file mode 100644 index 00000000000000..66e06a82b33b48 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java @@ -0,0 +1,211 @@ +package com.google.devtools.build.lib.sandbox.cgroups; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.flogger.GoogleLogger; +import com.google.common.io.CharSink; +import com.google.common.io.Files; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.sandbox.cgroups.v1.LegacyCpu; +import com.google.devtools.build.lib.sandbox.cgroups.v1.LegacyMemory; +import com.google.devtools.build.lib.sandbox.cgroups.v2.UnifiedCpu; +import com.google.devtools.build.lib.sandbox.cgroups.v2.UnifiedMemory; + +import javax.annotation.Nullable; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Scanner; +import java.util.concurrent.ConcurrentLinkedQueue; + + +/** + * This class creates and exposes a virtual cgroup for the bazel process and allows creating + * child cgroups. Resources are exposed as {@link Controller}s, each representing a + * subsystem within the virtual cgroup and that could in theory belong to different real cgroups. + */ +@AutoValue +public abstract class VirtualCGroup { + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + private static final File PROC_SELF_MOUNTS_PATH = new File("/proc/self/mounts"); + private static final File PROC_SELF_CGROUP_PATH = new File("/proc/self/cgroup"); + + @Nullable + private static volatile VirtualCGroup instance; + + public abstract Controller.Cpu cpu(); + public abstract Controller.Memory memory(); + public abstract ImmutableSet paths(); + + private final Queue children = new ConcurrentLinkedQueue<>(); + + public static VirtualCGroup getInstance(EventHandler reporter) throws IOException { + if (instance == null) { + synchronized (VirtualCGroup.class) { + if (instance == null) { + instance = create(reporter); + } + } + } + return instance; + } + + public static void deleteInstance() { + if (instance != null) { + synchronized (VirtualCGroup.class) { + if (instance != null) { + instance.delete(); + instance = null; + } + } + } + } + + public static VirtualCGroup create(EventHandler reporter) throws IOException { + return create(PROC_SELF_MOUNTS_PATH, PROC_SELF_CGROUP_PATH, reporter); + } + + private static void copyControllersToSubtree(Path cgroup) throws IOException { + File subtree = cgroup.resolve("cgroup.subtree_control").toFile(); + File controllers = cgroup.resolve("cgroup.controllers").toFile(); + if (subtree.canWrite() && controllers.canRead()) { + CharSink sink = Files.asCharSink(subtree, StandardCharsets.UTF_8); + Scanner scanner = new Scanner(controllers); + while (scanner.hasNext()) { + sink.write("+" + scanner.next()); + } + } + } + + static VirtualCGroup create(File procMounts, File procCgroup, EventHandler reporter) throws IOException { + final List mounts = Mount.parse(procMounts); + final Map hierarchies = Hierarchy.parse(procCgroup) + .stream() + .flatMap(h -> h.controllers().stream().map(c -> Map.entry(c, h))) + // For cgroup v2, there are no controllers specified in the proc/pid/cgroup file + // So the keep will be empty and unique. For cgroup v1, there could potentially + // be multiple mounting points for the same controller, but they represent a + // "view of the same hierarchy" so it is ok to just keep one. + // Ref. https://man7.org/linux/man-pages/man7/cgroups.7.html + .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + + Controller.Memory memory = null; + Controller.Cpu cpu = null; + ImmutableSet.Builder paths = ImmutableSet.builder(); + + for (Mount m: mounts) { + if (memory != null && cpu != null) break; + + if (m.isV2()) { + Hierarchy h = hierarchies.get(""); + if (h == null) continue; + Path cgroup = m.path().resolve(Paths.get("/").relativize(h.path())); + if (!cgroup.equals(m.path())) { + // Because of the "no internal processes" rule, it is not possible to + // create a non-empty child cgroups on non-root cgroups with member processes + // Instead, we go up one level in the hierarchy and declare a sibling. + cgroup = cgroup.getParent(); + } + if (!cgroup.toFile().canWrite()) { + reporter.handle(Event.warn("Found non-writable cgroup v2 at " + cgroup)); + continue; + } + try (InputStream s = new FileInputStream(cgroup.resolve("cgroup.procs").toFile())) { + // Check if the cgroup is empty, i.e. there are no member processes + // before modifying the subtree control to respect the "no internal processes" + if (s.read() != -1) { + reporter.handle(Event.warn("Found non-empty cgroup v2 at " + cgroup)); + continue; + } + copyControllersToSubtree(cgroup); + } + + cgroup = cgroup.resolve("bazel_" + ProcessHandle.current().pid() + ".slice"); + cgroup.toFile().mkdirs(); + paths.add(cgroup); + + Scanner scanner = new Scanner(cgroup.resolve("cgroup.controllers").toFile()); + while (scanner.hasNext()) { + switch (scanner.next()) { + case "memory": + if (memory != null) continue; + logger.atInfo().log("Found cgroup v2 memory controller at %s", cgroup); + memory = new UnifiedMemory(cgroup); + break; + case "cpu": + if (cpu != null) continue; + logger.atInfo().log("Found cgroup v2 cpu controller at %s", cgroup); + cpu = new UnifiedCpu(cgroup); + break; + } + } + } else { + for (String opt : m.opts()) { + Hierarchy h = hierarchies.get(opt); + if (h == null) continue; + Path cgroup = m.path().resolve(Paths.get("/").relativize(h.path())); + if (!cgroup.toFile().canWrite()) { + reporter.handle(Event.warn("Found non-writable cgroup v1 at " + cgroup)); + continue; + } + cgroup = cgroup.resolve("bazel_" + ProcessHandle.current().pid() + ".slice"); + cgroup.toFile().mkdirs(); + paths.add(cgroup); + + switch (opt) { + case "memory": + if (memory != null) continue; + logger.atInfo().log("Found cgroup v1 memory controller at %s", cgroup); + memory = new LegacyMemory(cgroup); + break; + case "cpu": + if (cpu != null) continue; + logger.atInfo().log("Found cgroup v1 cpu controller at %s", cgroup); + cpu = new LegacyCpu(cgroup); + break; + } + } + } + } + + cpu = cpu != null ? cpu : Controller.getDefault(Controller.Cpu.class); + memory = memory != null ? memory : Controller.getDefault(Controller.Memory.class); + VirtualCGroup vcgroup = new AutoValue_VirtualCGroup(cpu, memory, paths.build()); + Runtime.getRuntime().addShutdownHook(new Thread(() -> vcgroup.delete())); + return vcgroup; + } + + public void delete() { + this.children.forEach(VirtualCGroup::delete); + this.paths().stream().map(Path::toFile).filter(File::exists).forEach(File::delete); + } + + public VirtualCGroup child(String name) throws IOException { + Controller.Cpu cpu = Controller.getDefault(Controller.Cpu.class); + Controller.Memory memory = Controller.getDefault(Controller.Memory.class); + ImmutableSet.Builder paths = ImmutableSet.builder(); + if (memory() != null && memory().getPath() != null) { + copyControllersToSubtree(memory().getPath()); + Path cgroup = memory().getPath().resolve(name); + cgroup.toFile().mkdirs(); + memory = memory().isLegacy() ? new LegacyMemory(cgroup) : new UnifiedMemory(cgroup); + paths.add(cgroup); + } + if (cpu() != null && cpu().getPath() != null) { + copyControllersToSubtree(cpu().getPath()); + Path cgroup = cpu().getPath().resolve(name); + cgroup.toFile().mkdirs(); + cpu = cpu().isLegacy() ? new LegacyCpu(cgroup) : new UnifiedCpu(cgroup); + paths.add(cgroup); + } + VirtualCGroup child = new AutoValue_VirtualCGroup(cpu, memory, paths.build()); + this.children.add(child); + return child; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyCpu.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyCpu.java new file mode 100644 index 00000000000000..cdcff1643ba070 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyCpu.java @@ -0,0 +1,33 @@ +package com.google.devtools.build.lib.sandbox.cgroups.v1; + +import com.google.devtools.build.lib.sandbox.cgroups.Controller; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class LegacyCpu implements Controller.Cpu { + private final Path path; + private final int period; + + public LegacyCpu(Path path) throws IOException { + this.path = path; + this.period = Integer.parseInt(Files.readString(path.resolve("cpu.cfs_period_us")).trim()); + } + + @Override + public Path getPath() { + return path; + } + + @Override + public void setCpus(float cpus) throws IOException { + int quota = Math.round(cpus * period); + Files.writeString(path.resolve("cpu.cfs_quota_us"), Integer.toString(quota)); + } + + @Override + public int getCpus() throws IOException { + return Integer.parseInt(Files.readString(path.resolve("cpu.cfs_quota_us")).trim()); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyMemory.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyMemory.java new file mode 100644 index 00000000000000..873f728ccfde19 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyMemory.java @@ -0,0 +1,30 @@ +package com.google.devtools.build.lib.sandbox.cgroups.v1; + +import com.google.devtools.build.lib.sandbox.cgroups.Controller; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class LegacyMemory implements Controller.Memory { + private final Path path; + + @Override + public Path getPath() { + return path; + } + + public LegacyMemory(Path path) { + this.path = path; + } + + @Override + public void setMaxBytes(long bytes) throws IOException { + Files.writeString(path.resolve("memory.limit_in_bytes"), Long.toString(bytes)); + } + + @Override + public long getMaxBytes() throws IOException { + return Long.parseLong(Files.readString(path.resolve("memory.limit_in_bytes")).trim()); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedCpu.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedCpu.java new file mode 100644 index 00000000000000..a3711f845c80cb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedCpu.java @@ -0,0 +1,32 @@ +package com.google.devtools.build.lib.sandbox.cgroups.v2; + +import com.google.devtools.build.lib.sandbox.cgroups.Controller; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class UnifiedCpu implements Controller.Cpu { + private final Path path; + public UnifiedCpu(Path path) { + this.path = path; + } + + @Override + public Path getPath() { + return path; + } + + @Override + public void setCpus(float cpus) throws IOException { + int period = 1000_000; + int quota = Math.round(period * cpus); + String limit = String.format("%d %d", quota, period); + Files.writeString(path.resolve("cpu.max"), limit); + } + + @Override + public int getCpus() throws IOException { + return Integer.parseInt(Files.readString(path.resolve("cpu.max")).trim()); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedMemory.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedMemory.java new file mode 100644 index 00000000000000..3e6e23e1d7e9bb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v2/UnifiedMemory.java @@ -0,0 +1,29 @@ +package com.google.devtools.build.lib.sandbox.cgroups.v2; + +import com.google.devtools.build.lib.sandbox.cgroups.Controller; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class UnifiedMemory implements Controller.Memory { + private final Path path; + public UnifiedMemory(Path path) { + this.path = path; + } + + @Override + public Path getPath() { + return path; + } + + @Override + public void setMaxBytes(long bytes) throws IOException { + Files.writeString(path.resolve("memory.max"), Long.toString(bytes)); + } + + @Override + public long getMaxBytes() throws IOException { + return Long.parseLong(Files.readString(path.resolve("memory.max")).trim()); + } +} From c89bb0426362b61a9ef9ac29109cb302f282874c Mon Sep 17 00:00:00 2001 From: Alessandro Patti Date: Tue, 8 Aug 2023 16:28:20 +0200 Subject: [PATCH 2/6] Integrate --- .../google/devtools/build/lib/sandbox/BUILD | 2 + .../LinuxSandboxCommandLineBuilder.java | 10 ++-- .../sandbox/LinuxSandboxedSpawnRunner.java | 53 +++++++++++++------ .../build/lib/sandbox/SandboxOptions.java | 12 +++++ .../google/devtools/build/lib/worker/BUILD | 2 + .../build/lib/worker/SandboxedWorker.java | 24 +++++---- .../build/lib/worker/WorkerModule.java | 3 +- src/main/tools/linux-sandbox-options.cc | 3 +- src/main/tools/linux-sandbox-options.h | 4 +- src/main/tools/linux-sandbox-pid1.cc | 11 ++-- .../LinuxSandboxCommandLineBuilderTest.java | 8 +-- 11 files changed, 90 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD index e6e5f7ab7c4c0e..37bec0badb7c8a 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD +++ b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD @@ -31,6 +31,7 @@ java_library( deps = [ "//src/main/java/com/google/devtools/build/lib/actions:localhost_capacity", "//src/main/java/com/google/devtools/build/lib/util", + "//src/main/java/com/google/devtools/build/lib/util:cpu_resource_converter", "//src/main/java/com/google/devtools/build/lib/util:ram_resource_converter", "//src/main/java/com/google/devtools/build/lib/util:resource_converter", "//src/main/java/com/google/devtools/build/lib/vfs", @@ -247,6 +248,7 @@ java_library( "//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/profiler", + "//src/main/java/com/google/devtools/build/lib/sandbox/cgroups", "//src/main/java/com/google/devtools/build/lib/shell", "//src/main/java/com/google/devtools/build/lib/util:os", "//src/main/java/com/google/devtools/build/lib/util:pair", diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java index 6b355ec810d447..d4b9ea417a645d 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java @@ -53,7 +53,7 @@ public class LinuxSandboxCommandLineBuilder { private boolean enablePseudoterminal = false; private boolean useDebugMode = false; private boolean sigintSendsSigterm = false; - private String cgroupsDir; + private Set cgroupsDirs = ImmutableSet.of(); private LinuxSandboxCommandLineBuilder(Path linuxSandboxPath, List commandArguments) { this.linuxSandboxPath = linuxSandboxPath; @@ -214,8 +214,8 @@ public LinuxSandboxCommandLineBuilder setUseDebugMode(boolean useDebugMode) { * this directory, its parent directory, and the cgroup directory for the Bazel process. */ @CanIgnoreReturnValue - public LinuxSandboxCommandLineBuilder setCgroupsDir(String cgroupsDir) { - this.cgroupsDir = cgroupsDir; + public LinuxSandboxCommandLineBuilder setCgroupsDirs(Set cgroupsDirs) { + this.cgroupsDirs = cgroupsDirs; return this; } @@ -296,8 +296,8 @@ public ImmutableList build() { if (persistentProcess) { commandLineBuilder.add("-p"); } - if (cgroupsDir != null) { - commandLineBuilder.add("-C", cgroupsDir); + for (Path dir: cgroupsDirs) { + commandLineBuilder.add("-C", dir.toString()); } commandLineBuilder.add("--"); commandLineBuilder.addAll(commandArguments); diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java index c06cee94fdad8c..ed536d57beb032 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java @@ -42,6 +42,7 @@ import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs; import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs; import com.google.devtools.build.lib.server.FailureDetails.Sandbox.Code; +import com.google.devtools.build.lib.sandbox.cgroups.VirtualCGroup; import com.google.devtools.build.lib.shell.Command; import com.google.devtools.build.lib.shell.CommandException; import com.google.devtools.build.lib.util.OS; @@ -130,7 +131,6 @@ private static boolean computeIsSupported(CommandEnvironment cmdEnv, Path linuxS private final boolean sandboxfsMapSymlinkTargets; private final TreeDeleter treeDeleter; private final Reporter reporter; - private String cgroupsDir; /** * Creates a sandboxed spawn runner that uses the {@code linux-sandbox} tool. @@ -173,6 +173,33 @@ private static boolean computeIsSupported(CommandEnvironment cmdEnv, Path linuxS this.reporter = cmdEnv.getReporter(); } + private VirtualCGroup getCgroup(Spawn spawn, SpawnExecutionContext context) throws ExecException, IOException { + SandboxOptions sandboxOptions = getSandboxOptions(); + + VirtualCGroup cgroup = null; + // We put the sandbox inside a unique subdirectory using the context's ID. This ID is + // unique per spawn run by this spawn runner. + String name = "sandbox_" + context.getId() + ".scope"; + long memoryLimit = sandboxOptions.memoryLimitMb * 1024L * 1024L; + float cpuLimit = sandboxOptions.cpuLimit; + + if (memoryLimit > 0) { + if (cgroup == null) { + cgroup = VirtualCGroup.getInstance(this.reporter).child(name); + } + cgroup.memory().setMaxBytes(memoryLimit); + } + + if (cpuLimit > 0) { + if (cgroup == null) { + cgroup = VirtualCGroup.getInstance(this.reporter).child(name); + } + cgroup.cpu().setCpus(cpuLimit); + } + + return cgroup; + } + @Override protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context) throws IOException, ForbiddenActionInputException, ExecException, InterruptedException { @@ -243,14 +270,13 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context .setUseDebugMode(sandboxOptions.sandboxDebug) .setKillDelay(timeoutKillDelay); - if (sandboxOptions.memoryLimitMb > 0) { - CgroupsInfo cgroupsInfo = CgroupsInfo.getInstance(); - // We put the sandbox inside a unique subdirectory using the context's ID. This ID is - // unique per spawn run by this spawn runner. - cgroupsDir = - cgroupsInfo.createMemoryLimitCgroupDir( - "sandbox_" + context.getId(), sandboxOptions.memoryLimitMb); - commandLineBuilder.setCgroupsDir(cgroupsDir); + + VirtualCGroup cgroup = getCgroup(spawn, context); + if (cgroup != null) { + commandLineBuilder.setCgroupsDirs( + cgroup.paths().stream() + .map(p -> fileSystem.getPath(p.toString())) + .collect(ImmutableSet.toImmutableSet())); } if (!timeout.isZero()) { @@ -322,11 +348,6 @@ protected ImmutableSet getWritableDirs(Path sandboxExecRoot, Map writableDirs = ImmutableSet.builder(); writableDirs.addAll(super.getWritableDirs(sandboxExecRoot, env)); - - if (getSandboxOptions().memoryLimitMb > 0) { - CgroupsInfo cgroupsInfo = CgroupsInfo.getInstance(); - writableDirs.add(fileSystem.getPath(cgroupsInfo.getMountPoint().getAbsolutePath())); - } FileSystem fs = sandboxExecRoot.getFileSystem(); writableDirs.add(fs.getPath("/dev/shm").resolveSymbolicLinks()); writableDirs.add(fs.getPath("/tmp")); @@ -508,9 +529,7 @@ private boolean wasModifiedSinceDigest(FileContentsProxy proxy, Path path) throw @Override public void cleanupSandboxBase(Path sandboxBase, TreeDeleter treeDeleter) throws IOException { - if (cgroupsDir != null) { - new File(cgroupsDir).delete(); - } + VirtualCGroup.deleteInstance(); // Delete the inaccessible files synchronously, bypassing the treeDeleter. They are only a // couple of files that can be deleted fast, and ensuring they are gone at the end of every // build avoids annoying permission denied errors if the user happens to run "rm -rf" on the diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java index 35d79ca5b41250..bfb70d56fa8e5d 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java @@ -19,6 +19,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.devtools.build.lib.actions.LocalHostCapacity; +import com.google.devtools.build.lib.util.CpuResourceConverter; import com.google.devtools.build.lib.util.OptionsUtils; import com.google.devtools.build.lib.util.RamResourceConverter; import com.google.devtools.build.lib.util.ResourceConverter; @@ -407,6 +408,17 @@ public ImmutableSet getInaccessiblePaths(FileSystem fs) { + " Requires cgroups v1 or v2 and permissions for the users to the cgroups dir.") public int memoryLimitMb; + @Option( + name = "experimental_sandbox_cpu_limit", + defaultValue = "0", + documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY, + effectTags = {OptionEffectTag.EXECUTION}, + converter = CpuResourceConverter.class, + help = + "If > 0, each Linux sandbox will be limited to the given amount of cpus." + + " Requires cgroups v1 or v2 and permissions for the users to the cgroups dir.") + public float cpuLimit; + /** Converter for the number of threads used for asynchronous tree deletion. */ public static final class AsyncTreeDeletesConverter extends ResourceConverter { public AsyncTreeDeletesConverter() { 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 3dcd93ccbb63c2..4879f192685d3c 100644 --- a/src/main/java/com/google/devtools/build/lib/worker/BUILD +++ b/src/main/java/com/google/devtools/build/lib/worker/BUILD @@ -245,9 +245,11 @@ java_library( ":worker_exec_root", ":worker_key", ":worker_protocol", + "//src/main/java/com/google/devtools/build/lib/events", "//src/main/java/com/google/devtools/build/lib/sandbox:linux_sandbox", "//src/main/java/com/google/devtools/build/lib/sandbox:linux_sandbox_command_line_builder", "//src/main/java/com/google/devtools/build/lib/sandbox:sandbox_helpers", + "//src/main/java/com/google/devtools/build/lib/sandbox/cgroups", "//src/main/java/com/google/devtools/build/lib/shell", "//src/main/java/com/google/devtools/build/lib/vfs", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", diff --git a/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java b/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java index a3f838840d4afe..cf3229945c291e 100644 --- a/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java +++ b/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java @@ -23,10 +23,11 @@ import com.google.common.collect.Maps; import com.google.common.flogger.GoogleLogger; import com.google.common.io.Files; -import com.google.devtools.build.lib.sandbox.CgroupsInfo; +import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.sandbox.LinuxSandboxCommandLineBuilder; import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs; import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs; +import com.google.devtools.build.lib.sandbox.cgroups.VirtualCGroup; import com.google.devtools.build.lib.shell.Subprocess; import com.google.devtools.build.lib.shell.SubprocessBuilder; import com.google.devtools.build.lib.vfs.FileSystem; @@ -63,6 +64,8 @@ public abstract static class WorkerSandboxOptions { abstract int memoryLimit(); + abstract EventHandler reporter(); + public static WorkerSandboxOptions create( Path sandboxBinary, boolean fakeHostname, @@ -70,7 +73,8 @@ public static WorkerSandboxOptions create( boolean debugMode, ImmutableList tmpfsPath, ImmutableList writablePaths, - int memoryLimit) { + int memoryLimit, + EventHandler reporter) { return new AutoValue_SandboxedWorker_WorkerSandboxOptions( fakeHostname, fakeUsername, @@ -78,7 +82,8 @@ public static WorkerSandboxOptions create( tmpfsPath, writablePaths, sandboxBinary, - memoryLimit); + memoryLimit, + reporter); } } @@ -199,12 +204,13 @@ protected Subprocess createProcess() throws IOException, InterruptedException { .setCreateNetworkNamespace(true) .setUseDebugMode(hardenedSandboxOptions.debugMode()); if (hardenedSandboxOptions.memoryLimit() > 0) { - CgroupsInfo cgroupsInfo = CgroupsInfo.getInstance(); - // We put the sandbox inside a unique subdirectory using the worker's ID. - cgroupsDir = - cgroupsInfo.createMemoryLimitCgroupDir( - "worker_sandbox_" + workerId, hardenedSandboxOptions.memoryLimit()); - commandLineBuilder.setCgroupsDir(cgroupsDir); + String name = "worker_sandbox_" + workerId; + VirtualCGroup cgroup = + VirtualCGroup.getInstance(hardenedSandboxOptions.reporter()).child(name); + cgroup.memory().setMaxBytes(hardenedSandboxOptions.memoryLimit() * 1024L * 1024L); + ImmutableSet.Builder paths = ImmutableSet.builder(); + cgroup.paths().forEach(p -> paths.add(workDir.getFileSystem().getPath(p.toString()))); + commandLineBuilder.setCgroupsDirs(paths.build()); } if (this.hardenedSandboxOptions.fakeUsername()) { 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 4c8f7a7b98dad5..39c15be97fa486 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 @@ -100,7 +100,8 @@ public void buildStarting(BuildStartingEvent event) { sandboxOptions.sandboxDebug, ImmutableList.copyOf(sandboxOptions.sandboxTmpfsPath), ImmutableList.copyOf(sandboxOptions.sandboxWritablePath), - sandboxOptions.memoryLimitMb); + sandboxOptions.memoryLimitMb, + env.getReporter()); } else { workerSandboxOptions = null; } diff --git a/src/main/tools/linux-sandbox-options.cc b/src/main/tools/linux-sandbox-options.cc index 971b4d6333db22..c5dafbf6ca89e2 100644 --- a/src/main/tools/linux-sandbox-options.cc +++ b/src/main/tools/linux-sandbox-options.cc @@ -223,7 +223,8 @@ static void ParseCommandLine(unique_ptr> args) { break; case 'C': ValidateIsAbsolutePath(optarg, args->front(), static_cast(c)); - opt.cgroups_dir.assign(optarg); + opt.cgroups_dirs.emplace_back(optarg); + opt.writable_files.emplace_back(optarg); break; case 'P': opt.enable_pty = true; diff --git a/src/main/tools/linux-sandbox-options.h b/src/main/tools/linux-sandbox-options.h index 2a9d36b6ddfddb..0b9245b1f82d99 100644 --- a/src/main/tools/linux-sandbox-options.h +++ b/src/main/tools/linux-sandbox-options.h @@ -63,8 +63,8 @@ struct Options { bool hermetic; // The sandbox root directory (-s) std::string sandbox_root; - // Directory to use for cgroup control - std::string cgroups_dir; + // Directories to use for cgroup control + std::vector cgroups_dirs; // Command to run (--) std::vector args; }; diff --git a/src/main/tools/linux-sandbox-pid1.cc b/src/main/tools/linux-sandbox-pid1.cc index 6c346168208cbc..edb6d2baaf7046 100644 --- a/src/main/tools/linux-sandbox-pid1.cc +++ b/src/main/tools/linux-sandbox-pid1.cc @@ -343,7 +343,7 @@ static bool ShouldBeWritable(const std::string &mnt_dir) { return true; } - if (mnt_dir == "/sys/fs/cgroup" && !opt.cgroups_dir.empty()) { + if (mnt_dir == "/sys/fs/cgroup" && !opt.cgroups_dirs.empty()) { return true; } @@ -563,9 +563,12 @@ static int WaitForChild() { } static void AddProcessToCgroup() { - if (!opt.cgroups_dir.empty()) { - PRINT_DEBUG("Adding process to cgroups dir %s", opt.cgroups_dir.c_str()); - WriteFile(opt.cgroups_dir + "/cgroup.procs", "1"); + for(const std::string &cgroups_dir : opt.cgroups_dirs) { + PRINT_DEBUG("Adding process to cgroup dir %s", cgroups_dir.c_str()); + // Writing the value 0 to a cgroup.procs file causes the writing + // process to be moved to the corresponding cgroup. + // Ref. https://man7.org/linux/man-pages/man7/cgroups.7.html + WriteFile(cgroups_dir + "/cgroup.procs", "0"); } } diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java index 2eea4118aa081d..b844c88353e535 100644 --- a/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java +++ b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java @@ -25,6 +25,8 @@ 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 java.nio.file.Paths; import java.time.Duration; import java.util.List; import org.junit.Before; @@ -130,7 +132,7 @@ public void testLinuxSandboxCommandLineBuilder_buildsWithOptionalArguments() { .put(bindMountSameSourceAndTarget, bindMountSameSourceAndTarget) .buildOrThrow(); - String cgroupsDir = "/sys/fs/cgroups/something"; + Path cgroupsDir = fileSystem.getPath("/sys/fs/cgroups/something"); ImmutableList expectedCommandLine = ImmutableList.builder() @@ -155,7 +157,7 @@ public void testLinuxSandboxCommandLineBuilder_buildsWithOptionalArguments() { .add("-U") .add("-D") .add("-p") - .add("-C", cgroupsDir) + .add("-C", cgroupsDir.toString()) .add("--") .addAll(commandArguments) .build(); @@ -177,7 +179,7 @@ public void testLinuxSandboxCommandLineBuilder_buildsWithOptionalArguments() { .setUseFakeUsername(useFakeUsername) .setUseDebugMode(useDebugMode) .setPersistentProcess(true) - .setCgroupsDir(cgroupsDir) + .setCgroupsDirs(ImmutableSet.of(cgroupsDir)) .build(); assertThat(commandLine).containsExactlyElementsIn(expectedCommandLine).inOrder(); From 7863cedd543f943e04834696e5576187796669a5 Mon Sep 17 00:00:00 2001 From: Alessandro Patti Date: Wed, 9 Aug 2023 20:03:31 +0200 Subject: [PATCH 3/6] Support setting requirements with exec info --- .../lib/actions/ExecutionRequirements.java | 3 + .../google/devtools/build/lib/sandbox/BUILD | 1 + .../sandbox/LinuxSandboxedSpawnRunner.java | 65 +++++++++++++++++-- .../build/lib/sandbox/SandboxOptions.java | 12 ++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java b/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java index 3a24ecfdb0b3ce..bfccbda8366b62 100644 --- a/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java +++ b/src/main/java/com/google/devtools/build/lib/actions/ExecutionRequirements.java @@ -300,4 +300,7 @@ public enum WorkerProtocolFormat { /** Requires the execution service do NOT share caches across different workspace. */ public static final String DIFFERENTIATE_WORKSPACE_CACHE = "internal-differentiate-workspace-cache"; + + /** Disables cgroups for a spawn */ + public static final String NO_SUPPORTS_CGROUPS = "no-supports-cgroups"; } diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD index 37bec0badb7c8a..0e80f326e9c31e 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD +++ b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD @@ -237,6 +237,7 @@ java_library( ":sandboxfs_sandboxed_spawn", "//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:exec_exception", "//src/main/java/com/google/devtools/build/lib/actions:artifacts", "//src/main/java/com/google/devtools/build/lib/actions:execution_requirements", "//src/main/java/com/google/devtools/build/lib/actions:file_metadata", diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java index ed536d57beb032..0cc917dc1c8663 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java @@ -43,6 +43,7 @@ import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs; import com.google.devtools.build.lib.server.FailureDetails.Sandbox.Code; import com.google.devtools.build.lib.sandbox.cgroups.VirtualCGroup; +import com.google.devtools.build.lib.server.FailureDetails; import com.google.devtools.build.lib.shell.Command; import com.google.devtools.build.lib.shell.CommandException; import com.google.devtools.build.lib.util.OS; @@ -174,25 +175,79 @@ private static boolean computeIsSupported(CommandEnvironment cmdEnv, Path linuxS } private VirtualCGroup getCgroup(Spawn spawn, SpawnExecutionContext context) throws ExecException, IOException { + if (spawn.getExecutionInfo().get(ExecutionRequirements.NO_SUPPORTS_CGROUPS) != null) { + return null; + } SandboxOptions sandboxOptions = getSandboxOptions(); VirtualCGroup cgroup = null; - // We put the sandbox inside a unique subdirectory using the context's ID. This ID is - // unique per spawn run by this spawn runner. - String name = "sandbox_" + context.getId() + ".scope"; long memoryLimit = sandboxOptions.memoryLimitMb * 1024L * 1024L; float cpuLimit = sandboxOptions.cpuLimit; + if (sandboxOptions.executionInfoLimit) { + ExecutionRequirements.ParseableRequirement requirement = ExecutionRequirements.RESOURCES; + for (String tag : spawn.getExecutionInfo().keySet()) { + try { + requirement = ExecutionRequirements.RESOURCES; + String name = null; + Float value = null; + + String extras = requirement.parseIfMatches(tag); + if (extras != null) { + int index = extras.indexOf(":"); + name = extras.substring(0, index); + value = Float.parseFloat(extras.substring(index + 1)); + } else { + requirement = ExecutionRequirements.CPU; + String cpus = requirement.parseIfMatches(tag); + if (cpus != null) { + name = "cpu"; + value = Float.parseFloat(cpus); + } + } + if (name == null) { + continue; + } + switch (name) { + case "memory": + memoryLimit = Math.round(value * 1024.0 * 1024.0); + break; + case "cpu": + cpuLimit = value; + break; + } + } catch (ExecutionRequirements.ParseableRequirement.ValidationException e) { + String message = + String.format( + "%s has a '%s' tag, but its value '%s' didn't pass validation: %s", + spawn.getTargetLabel(), + requirement.userFriendlyName(), + e.getTagValue(), + e.getMessage()); + FailureDetails.Spawn.Code code = FailureDetails.Spawn.Code.COMMAND_LINE_EXPANSION_FAILURE; + FailureDetails.FailureDetail details = FailureDetails.FailureDetail + .newBuilder() + .setMessage(message) + .setSpawn(FailureDetails.Spawn.newBuilder().setCode(code)) + .build(); + throw new UserExecException(e, details); + } + } + } + + // We put the sandbox inside a unique subdirectory using the context's ID. This ID is + // unique per spawn run by this spawn runner. + String scope = "sandbox_" + context.getId() + ".scope"; if (memoryLimit > 0) { if (cgroup == null) { - cgroup = VirtualCGroup.getInstance(this.reporter).child(name); + cgroup = VirtualCGroup.getInstance(this.reporter).child(scope); } cgroup.memory().setMaxBytes(memoryLimit); } if (cpuLimit > 0) { if (cgroup == null) { - cgroup = VirtualCGroup.getInstance(this.reporter).child(name); + cgroup = VirtualCGroup.getInstance(this.reporter).child(scope); } cgroup.cpu().setCpus(cpuLimit); } diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java index bfb70d56fa8e5d..2f2b7ba7a96158 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java @@ -419,6 +419,18 @@ public ImmutableSet getInaccessiblePaths(FileSystem fs) { + " Requires cgroups v1 or v2 and permissions for the users to the cgroups dir.") public float cpuLimit; + @Option( + name = "experimental_sandbox_execution_info_limit", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY, + effectTags = {OptionEffectTag.EXECUTION}, + help = + "If true, resources declared in the execution info that match a cgroup controller" + + " will be used to apply the limits. For example a target that declares" + + " cpu:3 and resources:memory:10, will run with at most 3 cpus and 10" + + " megabytes of memory.") + public boolean executionInfoLimit; + /** Converter for the number of threads used for asynchronous tree deletion. */ public static final class AsyncTreeDeletesConverter extends ResourceConverter { public AsyncTreeDeletesConverter() { From 9b66c42b5b529aa34f549ff0934bfbd8ecd054b0 Mon Sep 17 00:00:00 2001 From: Alessandro Patti Date: Fri, 11 Aug 2023 10:11:27 +0200 Subject: [PATCH 4/6] Add support size-based test resources --- .../google/devtools/build/lib/analysis/BUILD | 2 + .../config/BuildConfigurationValue.java | 14 ++++ .../lib/analysis/test/TestConfiguration.java | 76 +++++++++++++++++++ .../analysis/test/TestTargetProperties.java | 29 +++++++ 4 files changed, 121 insertions(+) diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD index c8b032e3eb9730..c8eb946e6a90b1 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD +++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD @@ -1616,6 +1616,7 @@ java_library( ":config/invalid_configuration_exception", ":config/run_under", ":platform_options", + ":test/test_configuration", "//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/buildeventstream", @@ -1623,6 +1624,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/cmdline", "//src/main/java/com/google/devtools/build/lib/concurrent", "//src/main/java/com/google/devtools/build/lib/events", + "//src/main/java/com/google/devtools/build/lib/packages", "//src/main/java/com/google/devtools/build/lib/skyframe:build_configuration", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec", "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi", diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java index 16b508a4bbde74..3f6763a15e5245 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java @@ -28,6 +28,7 @@ import com.google.devtools.build.lib.analysis.BlazeDirectories; import com.google.devtools.build.lib.analysis.PlatformOptions; import com.google.devtools.build.lib.analysis.config.OutputDirectories.InvalidMnemonicException; +import com.google.devtools.build.lib.analysis.test.TestConfiguration; import com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildEventId; @@ -36,6 +37,7 @@ import com.google.devtools.build.lib.cmdline.RepositoryName; import com.google.devtools.build.lib.concurrent.BlazeInterners; import com.google.devtools.build.lib.events.EventHandler; +import com.google.devtools.build.lib.packages.TestSize; import com.google.devtools.build.lib.skyframe.BuildConfigurationKey; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.starlarkbuildapi.BuildConfigurationApi; @@ -901,4 +903,16 @@ private BuildConfigurationEvent createBuildEvent() { public ImmutableSet getReservedActionMnemonics() { return reservedActionMnemonics; } + + public Map getTestResources(TestSize size) { + if (!buildOptions.contains(TestConfiguration.TestOptions.class)) { + return ImmutableMap.of(); + } + return buildOptions + .get(TestConfiguration.TestOptions.class) + .testResources + .stream() + .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, e -> e.getValue().get(size))); + } + } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java index df7ea2ba3da01b..0235fe0b8a30da 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java @@ -14,10 +14,13 @@ package com.google.devtools.build.lib.analysis.test; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.devtools.build.lib.analysis.OptionsDiffPredicate; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.CoreOptionConverters.LabelConverter; @@ -28,8 +31,10 @@ import com.google.devtools.build.lib.analysis.test.CoverageConfiguration.CoverageOptions; import com.google.devtools.build.lib.analysis.test.TestShardingStrategy.ShardingStrategyConverter; import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.packages.TestSize; import com.google.devtools.build.lib.packages.TestTimeout; import com.google.devtools.build.lib.util.RegexFilter; +import com.google.devtools.common.options.Converter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDefinition; import com.google.devtools.common.options.OptionDocumentationCategory; @@ -39,7 +44,9 @@ import com.google.devtools.common.options.OptionsParsingException; import com.google.devtools.common.options.TriState; import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; +import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -84,6 +91,23 @@ public static class TestOptions extends FragmentOptions { + "-1 tells blaze to use its default timeouts for that category.") public Map testTimeout; + @Option( + name = "experimental_test_resources", + defaultValue = "null", + converter = TestResourcesConverter.class, + allowMultiple = true, + documentationCategory = OptionDocumentationCategory.TESTING, + effectTags = {OptionEffectTag.UNKNOWN}, + help = + "Override the default resources amount for tests. The expected format is" + + " =. If a single positive float is specified as " + + " it will override the default resources for all test sizes. If 4" + + " comma-separated flats are specified, they will override the resource" + + " amount for small, medium, large, enourmous (in that order)." + + " Multiple resources can be separated by colon and multiple usages" + + " are accumulated.") + public List>> testResources; + @Option( name = "test_filter", allowMultiple = false, @@ -450,4 +474,56 @@ public String getTypeDescription() { return "a positive integer or test_regex@runs. This flag may be passed more than once"; } } + + public static class TestResourcesConverter extends Converter.Contextless>>> { + @Override + public String getTypeDescription() { + return "a resource name followed by equal and 1 float or 4 float, e.g memory=10,30,60,100"; + } + + @Override + public List>> convert(String input) throws OptionsParsingException { + ImmutableList.Builder>> resources = ImmutableList.builder(); + Map requests; + char resourcesSep = ':', valueSep = '='; + + try { + requests = Splitter.on(resourcesSep).withKeyValueSeparator(valueSep).split(input); + } catch (IllegalArgumentException e) { + String message = String.format( + "%s. Separate resources from each other with '%c' and resources" + + " from their values with '%c', e.g. memory=10,20,50,100:cpu=1", + e.getMessage(), + resourcesSep, + valueSep); + throw new OptionsParsingException(message, e); + } + for (Map.Entry request: requests.entrySet()) { + List values = new ArrayList<>(); + for (String token: Splitter.on(",").omitEmptyStrings().limit(5).split(request.getValue())) { + try { + values.add(Float.parseFloat(token)); + } catch (NumberFormatException e) { + throw new OptionsParsingException("'" + token + "' is not a float", e); + } + } + EnumMap amounts = Maps.newEnumMap(TestSize.class); + if (values.size() == 1) { + amounts.put(TestSize.SMALL, values.get(0)); + amounts.put(TestSize.MEDIUM, values.get(0)); + amounts.put(TestSize.LARGE, values.get(0)); + amounts.put(TestSize.ENORMOUS, values.get(0)); + } else if (values.size() == 4) { + amounts.put(TestSize.SMALL, values.get(0)); + amounts.put(TestSize.MEDIUM, values.get(1)); + amounts.put(TestSize.LARGE, values.get(2)); + amounts.put(TestSize.ENORMOUS, values.get(3)); + } else { + throw new OptionsParsingException("Invalid number of comma-separated entries"); + } + resources.add(Maps.immutableEntry(request.getKey(), amounts)); + } + return resources.build(); + } + } } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java index 0a78cc351ff566..e9d99cd2e17c83 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestTargetProperties.java @@ -16,6 +16,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.devtools.build.lib.actions.ExecutionRequirements; import com.google.devtools.build.lib.actions.ExecutionRequirements.ParseableRequirement.ValidationException; @@ -34,6 +35,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Container for test target properties available to the @@ -88,9 +90,36 @@ private static ResourceSet getResourceSetFromSize(TestSize size) { isFlaky = ruleContext.attributes().get("flaky", Type.BOOLEAN); isExternal = TargetUtils.isExternalTestRule(rule); + Map executionInfo = Maps.newLinkedHashMap(); executionInfo.putAll(TargetUtils.getExecutionInfo(rule)); + ImmutableSet requestedResources = executionInfo.keySet() + .stream() + .map(tag -> { + try { + String value = ExecutionRequirements.RESOURCES.parseIfMatches(tag); + if (value != null) { + return value.substring(0, value.indexOf(":")); + } else if (ExecutionRequirements.CPU.parseIfMatches(tag) != null) { + return "cpu"; + } + } catch (ValidationException e) { + return null; + } + return null; + }) + .filter(Objects::nonNull) + .collect(ImmutableSet.toImmutableSet()); + + Map testResources = ruleContext.getConfiguration().getTestResources(size); + for (Map.Entry request: testResources.entrySet()) { + if (requestedResources.contains(request.getKey())) { + continue; + } + executionInfo.put(String.format("resources:%s:%f", request.getKey(), request.getValue()), ""); + } + boolean incompatibleExclusiveTestSandboxed = false; TestConfiguration testConfiguration = ruleContext.getFragment(TestConfiguration.class); From 1cb3212e5ab3aafcb337d1572fb83b3fa10977c5 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 27 Aug 2023 23:33:01 -0700 Subject: [PATCH 5/6] . --- .../sandbox/LinuxSandboxedSpawnRunner.java | 10 +++++++ .../build/lib/sandbox/cgroups/Controller.java | 4 +++ .../lib/sandbox/cgroups/VirtualCGroup.java | 20 +++++++++++-- .../lib/sandbox/cgroups/v1/LegacyNetCls.java | 30 +++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyNetCls.java diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java index 0cc917dc1c8663..a48e4a28448e1b 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java @@ -183,10 +183,14 @@ private VirtualCGroup getCgroup(Spawn spawn, SpawnExecutionContext context) thro VirtualCGroup cgroup = null; long memoryLimit = sandboxOptions.memoryLimitMb * 1024L * 1024L; float cpuLimit = sandboxOptions.cpuLimit; + boolean requiresNetwork = false; if (sandboxOptions.executionInfoLimit) { ExecutionRequirements.ParseableRequirement requirement = ExecutionRequirements.RESOURCES; for (String tag : spawn.getExecutionInfo().keySet()) { + if (tag.equals(ExecutionRequirements.REQUIRES_NETWORK)){ + requiresNetwork = true; + } try { requirement = ExecutionRequirements.RESOURCES; String name = null; @@ -251,6 +255,12 @@ private VirtualCGroup getCgroup(Spawn spawn, SpawnExecutionContext context) thro } cgroup.cpu().setCpus(cpuLimit); } + if (!requiresNetwork) { + if (cgroup == null) { + cgroup = VirtualCGroup.getInstance(this.reporter).child(scope); + } + cgroup.netCls().setNetCls(1337); + } return cgroup; } diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java index 23aa9ed2151e87..75a349423ddfcc 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/Controller.java @@ -41,4 +41,8 @@ interface Cpu extends Controller { void setCpus(float cpus) throws IOException; int getCpus() throws IOException; } + interface NetCls extends Controller { + void setNetCls(int netCls) throws IOException; + int getNetCls() throws IOException; + } } \ No newline at end of file diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java index 66e06a82b33b48..ba1c8e68de7198 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/VirtualCGroup.java @@ -10,6 +10,7 @@ import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.sandbox.cgroups.v1.LegacyCpu; import com.google.devtools.build.lib.sandbox.cgroups.v1.LegacyMemory; +import com.google.devtools.build.lib.sandbox.cgroups.v1.LegacyNetCls; import com.google.devtools.build.lib.sandbox.cgroups.v2.UnifiedCpu; import com.google.devtools.build.lib.sandbox.cgroups.v2.UnifiedMemory; @@ -41,6 +42,7 @@ public abstract class VirtualCGroup { public abstract Controller.Cpu cpu(); public abstract Controller.Memory memory(); + public abstract Controller.NetCls netCls(); public abstract ImmutableSet paths(); private final Queue children = new ConcurrentLinkedQueue<>(); @@ -97,6 +99,7 @@ static VirtualCGroup create(File procMounts, File procCgroup, EventHandler repor Controller.Memory memory = null; Controller.Cpu cpu = null; + Controller.NetCls netCls = null; ImmutableSet.Builder paths = ImmutableSet.builder(); for (Mount m: mounts) { @@ -169,6 +172,11 @@ static VirtualCGroup create(File procMounts, File procCgroup, EventHandler repor logger.atInfo().log("Found cgroup v1 cpu controller at %s", cgroup); cpu = new LegacyCpu(cgroup); break; + case "netcls": + if (netCls != null) continue; + logger.atInfo().log("Found cgroup v1 netcls controller at %s", cgroup); + netCls = new LegacyNetCls(cgroup); + break; } } } @@ -176,7 +184,7 @@ static VirtualCGroup create(File procMounts, File procCgroup, EventHandler repor cpu = cpu != null ? cpu : Controller.getDefault(Controller.Cpu.class); memory = memory != null ? memory : Controller.getDefault(Controller.Memory.class); - VirtualCGroup vcgroup = new AutoValue_VirtualCGroup(cpu, memory, paths.build()); + VirtualCGroup vcgroup = new AutoValue_VirtualCGroup(cpu, memory, netCls, paths.build()); Runtime.getRuntime().addShutdownHook(new Thread(() -> vcgroup.delete())); return vcgroup; } @@ -189,6 +197,7 @@ public void delete() { public VirtualCGroup child(String name) throws IOException { Controller.Cpu cpu = Controller.getDefault(Controller.Cpu.class); Controller.Memory memory = Controller.getDefault(Controller.Memory.class); + Controller.NetCls netCls = Controller.getDefault(Controller.NetCls.class); ImmutableSet.Builder paths = ImmutableSet.builder(); if (memory() != null && memory().getPath() != null) { copyControllersToSubtree(memory().getPath()); @@ -204,7 +213,14 @@ public VirtualCGroup child(String name) throws IOException { cpu = cpu().isLegacy() ? new LegacyCpu(cgroup) : new UnifiedCpu(cgroup); paths.add(cgroup); } - VirtualCGroup child = new AutoValue_VirtualCGroup(cpu, memory, paths.build()); + if (netCls() != null && netCls().getPath() != null) { + copyControllersToSubtree(netCls().getPath()); + Path cgroup = netCls().getPath().resolve(name); + cgroup.toFile().mkdirs(); + netCls = netCls().isLegacy() ? new LegacyNetCls(cgroup) : null; + paths.add(cgroup); + } + VirtualCGroup child = new AutoValue_VirtualCGroup(cpu, memory, netCls, paths.build()); this.children.add(child); return child; } diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyNetCls.java b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyNetCls.java new file mode 100644 index 00000000000000..632f8e58c47f2c --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/sandbox/cgroups/v1/LegacyNetCls.java @@ -0,0 +1,30 @@ +package com.google.devtools.build.lib.sandbox.cgroups.v1; + +import com.google.devtools.build.lib.sandbox.cgroups.Controller; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class LegacyNetCls implements Controller.NetCls { + private final Path path; + + @Override + public Path getPath() { + return path; + } + + public LegacyNetCls(Path path) { + this.path = path; + } + + @Override + public void setNetCls(int netCls) throws IOException { + Files.writeString(path.resolve("net_cls.classid"), Integer.toString(netCls)); + } + + @Override + public int getNetCls() throws IOException { + return Integer.parseInt(Files.readString(path.resolve("net_cls.classid")).trim()); + } +} From 6cd792611af91573b3b9e5b6c533d20bd1187e51 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 27 Aug 2023 23:40:19 -0700 Subject: [PATCH 6/6] configurable netcls --- .../build/lib/sandbox/LinuxSandboxedSpawnRunner.java | 8 ++++---- .../devtools/build/lib/sandbox/SandboxOptions.java | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java index a48e4a28448e1b..bdaf82b7dc5d01 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java @@ -183,13 +183,13 @@ private VirtualCGroup getCgroup(Spawn spawn, SpawnExecutionContext context) thro VirtualCGroup cgroup = null; long memoryLimit = sandboxOptions.memoryLimitMb * 1024L * 1024L; float cpuLimit = sandboxOptions.cpuLimit; - boolean requiresNetwork = false; + boolean requiresNetworkTag = false; if (sandboxOptions.executionInfoLimit) { ExecutionRequirements.ParseableRequirement requirement = ExecutionRequirements.RESOURCES; for (String tag : spawn.getExecutionInfo().keySet()) { if (tag.equals(ExecutionRequirements.REQUIRES_NETWORK)){ - requiresNetwork = true; + requiresNetworkTag = true; } try { requirement = ExecutionRequirements.RESOURCES; @@ -255,11 +255,11 @@ private VirtualCGroup getCgroup(Spawn spawn, SpawnExecutionContext context) thro } cgroup.cpu().setCpus(cpuLimit); } - if (!requiresNetwork) { + if (!requiresNetworkTag && sandboxOptions.sandboxNonRequiresNetworkCgroupNetCls != 0) { if (cgroup == null) { cgroup = VirtualCGroup.getInstance(this.reporter).child(scope); } - cgroup.netCls().setNetCls(1337); + cgroup.netCls().setNetCls(sandboxOptions.sandboxNonRequiresNetworkCgroupNetCls); } return cgroup; diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java index 2f2b7ba7a96158..aea004db447f78 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java @@ -348,6 +348,17 @@ public ImmutableSet getInaccessiblePaths(FileSystem fs) { + " grows to the size specified by this flag when the server is idle.") public int asyncTreeDeleteIdleThreads; + @Option( + name = "sandbox_non_requires_network_cgroup_net_cls", + defaultValue = "0", + converter = AsyncTreeDeletesConverter.class, + documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY, + effectTags = {OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS, OptionEffectTag.EXECUTION}, + help = + "If set, any target not tagged with requires-network will run its actions " + + "inside a sandbox with the given netcls for filtering by iptables firewalls") + public int sandboxNonRequiresNetworkCgroupNetCls; + @Option( name = "incompatible_legacy_local_fallback", defaultValue = "true",