Use prefix encoding for paths.
sandboxfs now has the ability to track directory paths separately from
each mapping request, essentially encoding paths as an integer that
identifies a directory and the file name within that directory.
This change adds support for this kind of encoding to cut down the
massive size of the reconfiguration messages. As a result, we use less
CPU time in Bazel and in sandboxfs just dealing with communication
overhead.
RELNOTES: None.
PiperOrigin-RevId: 298895132
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02Process.java b/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02Process.java
index f8b705e..60b9a4c 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02Process.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02Process.java
@@ -31,6 +31,8 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -160,6 +162,66 @@
}
}
+ /** A pair that represents the identifier and the path of a prefix as it will be serialized. */
+ private static class Prefix {
+ Integer id;
+ String serializedPath;
+
+ /**
+ * Constructs a new prefix from its components.
+ *
+ * @param id the numerical identifier of the prefix
+ * @param serializedPath the path of the prefix. Given that this is typically comes from a call
+ * to {@link Path#getParentDirectory()}, the value may be null or empty.
+ */
+ Prefix(Integer id, @Nullable PathFragment serializedPath) {
+ this.id = id;
+ if (serializedPath == null || serializedPath.isEmpty()) {
+ this.serializedPath = "/";
+ } else {
+ this.serializedPath = serializedPath.getPathString();
+ }
+ }
+ }
+
+ /** Registers and assigns unique identifiers to path prefixes. */
+ private static class Prefixes {
+ /** Collection of all known path prefixes to this sandboxfs instance. */
+ @GuardedBy("this")
+ private final Map<PathFragment, Integer> prefixes = new HashMap<>();
+
+ /** Last identifier assigned to a path prefix. */
+ @GuardedBy("this")
+ private Integer lastPrefixId = 1;
+
+ /**
+ * Registers the given path as a prefix.
+ *
+ * @param path the path to register as a prefix, which should always be the return value of a
+ * call to {@link Path#getParentDirectory()}. As a result, this should either be an absolute
+ * path, or a null or empty path.
+ * @param newPrefixes tracker for any new prefixes registered by this function. If the given
+ * path is not yet known, it will be added to this collection.
+ * @return the identifier for the prefix
+ */
+ private synchronized Integer registerPrefix(PathFragment path, ArrayList<Prefix> newPrefixes) {
+ Integer id = prefixes.get(path);
+ if (id != null) {
+ return id;
+ }
+
+ id = lastPrefixId;
+ prefixes.put(path, id);
+ newPrefixes.add(new Prefix(id, path));
+ lastPrefixId++;
+
+ return id;
+ }
+ }
+
+ /** Tracker for all known path prefixes to this sandboxfs instance. */
+ private final Prefixes allPrefixes = new Prefixes();
+
/**
* Initializes a new sandboxfs process instance.
*
@@ -291,15 +353,28 @@
processStdIn.value(id);
processStdIn.name("m");
processStdIn.beginArray();
+ ArrayList<Prefix> newPrefixes = new ArrayList<>();
creator.create(
(path, underlyingPath, writable) -> {
+ Integer pathPrefix =
+ allPrefixes.registerPrefix(path.getParentDirectory(), newPrefixes);
+ Integer underlyingPathPrefix =
+ allPrefixes.registerPrefix(underlyingPath.getParentDirectory(), newPrefixes);
+
+ // This synchronized block is theoretically unnecessary because the lock is already
+ // held by the caller of this lambda. However, we need to reenter it to appease
+ // the @GuardedBy clauses.
synchronized (this) {
processStdIn.beginObject();
{
+ processStdIn.name("x");
+ processStdIn.value(pathPrefix);
processStdIn.name("p");
- processStdIn.value(path.getPathString());
+ processStdIn.value(path.getBaseName());
+ processStdIn.name("y");
+ processStdIn.value(underlyingPathPrefix);
processStdIn.name("u");
- processStdIn.value(underlyingPath.getPathString());
+ processStdIn.value(underlyingPath.getBaseName());
if (writable) {
processStdIn.name("w");
processStdIn.value(writable);
@@ -309,6 +384,15 @@
}
});
processStdIn.endArray();
+ if (!newPrefixes.isEmpty()) {
+ processStdIn.name("q");
+ processStdIn.beginObject();
+ for (Prefix prefix : newPrefixes) {
+ processStdIn.name(prefix.id.toString());
+ processStdIn.value(prefix.serializedPath);
+ }
+ processStdIn.endObject();
+ }
}
processStdIn.endObject();
}
diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02ProcessTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02ProcessTest.java
index 41f617d..155fe1b 100644
--- a/src/test/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02ProcessTest.java
+++ b/src/test/java/com/google/devtools/build/lib/sandbox/RealSandboxfs02ProcessTest.java
@@ -50,7 +50,8 @@
"{\"id\":\"empty\"}",
"{\"id\":\"sandbox1\"}",
"{\"id\":\"sandbox2\"}",
- "{\"id\":\"sandbox1\"}"));
+ "{\"id\":\"sandbox1\"}",
+ "{\"id\":\"sandbox3\"}"));
process.createSandbox("sandbox1", (mapper) -> {});
process.createSandbox(
@@ -60,14 +61,30 @@
mapper.map(PathFragment.create("/a/b/c"), PathFragment.create("/other"), false);
});
process.destroySandbox("sandbox1");
+ process.createSandbox(
+ "sandbox3",
+ (mapper) -> {
+ // Reuse a previous prefix.
+ mapper.map(PathFragment.create("/a/b/c"), PathFragment.create("/"), true);
+ // And create a new prefix to ensure identifiers are not reset.
+ mapper.map(PathFragment.create("/a/b"), PathFragment.create("/"), false);
+ });
String expectedRequests =
"{\"C\":{\"i\":\"empty\",\"m\":[]}}"
+ "{\"C\":{\"i\":\"sandbox1\",\"m\":[]}}"
+ "{\"C\":{\"i\":\"sandbox2\",\"m\":["
- + "{\"p\":\"/\",\"u\":\"/some/path\",\"w\":true},"
- + "{\"p\":\"/a/b/c\",\"u\":\"/other\"}"
- + "]}}"
- + "{\"D\":\"sandbox1\"}";
+ + "{\"x\":1,\"p\":\"\",\"y\":2,\"u\":\"path\",\"w\":true},"
+ + "{\"x\":3,\"p\":\"c\",\"y\":4,\"u\":\"other\"}"
+ + "],"
+ + "\"q\":{\"1\":\"/\",\"2\":\"/some\",\"3\":\"/a/b\",\"4\":\"/\"}"
+ + "}}"
+ + "{\"D\":\"sandbox1\"}"
+ + "{\"C\":{\"i\":\"sandbox3\",\"m\":["
+ + "{\"x\":3,\"p\":\"c\",\"y\":1,\"u\":\"\",\"w\":true},"
+ + "{\"x\":5,\"p\":\"b\",\"y\":1,\"u\":\"\"}"
+ + "],"
+ + "\"q\":{\"5\":\"/a\"}"
+ + "}}";
verifyFakeSandboxfsExecution(process, expectedRequests);
}