Skip to content

Commit 24de276

Browse files
tjgqcopybara-github
authored andcommitted
Introduce options for remote cache key scrubbing.
Introduce an experimental flag for remote cache key scrubbing. This PR introduces the --experimental_remote_scrubbing_config flag, which can be used to scrub platform-dependent data from the key used to retrieve and store action results from a remote or disk cache This way, actions executing on different platforms but targeting the same platform may be able to share cache entries. To avoid flag proliferation and make for a nicer API, the scrubbing parameters are supplied via an external file in text proto format. This is a simplified implementation of one of the ideas described in [1], highly influenced by Olivier Notteghem's earlier proposal [2], intended to provide a simple yet flexible API to enable further experimentation. It must be used with care, as incorrect settings can compromise build correctness. [1] https://docs.google.com/document/d/1uMPj2s0TlHSIKSngqOkWJoeqOtKzaxQLtBrRfYif3Lo/edit?usp=sharing [2] #18669 Closes #19523. PiperOrigin-RevId: 571947013 Change-Id: Ic8da59946ea6d6811b840aee01548746aab2eba0
1 parent 3bb882d commit 24de276

File tree

20 files changed

+1004
-27
lines changed

20 files changed

+1004
-27
lines changed

src/main/java/com/google/devtools/build/lib/remote/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ java_library(
3737
"RemoteOutputChecker.java",
3838
"AbstractActionInputPrefetcher.java",
3939
"LeaseService.java",
40+
"Scrubber.java",
4041
"Store.java",
4142
],
4243
),
@@ -90,6 +91,7 @@ java_library(
9091
"//src/main/java/com/google/devtools/build/lib/exec/local",
9192
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
9293
"//src/main/java/com/google/devtools/build/lib/profiler",
94+
"//src/main/java/com/google/devtools/build/lib/remote:scrubber",
9395
"//src/main/java/com/google/devtools/build/lib/remote/circuitbreaker",
9496
"//src/main/java/com/google/devtools/build/lib/remote/common",
9597
"//src/main/java/com/google/devtools/build/lib/remote/common:bulk_transfer_exception",
@@ -252,6 +254,19 @@ java_library(
252254
],
253255
)
254256

257+
java_library(
258+
name = "scrubber",
259+
srcs = ["Scrubber.java"],
260+
deps = [
261+
"//src/main/java/com/google/devtools/build/lib/actions",
262+
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
263+
"//src/main/protobuf:remote_scrubbing_java_proto",
264+
"//third_party:guava",
265+
"//third_party:jsr305",
266+
"//third_party/protobuf:protobuf_java",
267+
],
268+
)
269+
255270
java_library(
256271
name = "store",
257272
srcs = ["Store.java"],

src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
import com.google.devtools.build.lib.remote.RemoteExecutionService.ActionResultMetadata.DirectoryMetadata;
8888
import com.google.devtools.build.lib.remote.RemoteExecutionService.ActionResultMetadata.FileMetadata;
8989
import com.google.devtools.build.lib.remote.RemoteExecutionService.ActionResultMetadata.SymlinkMetadata;
90+
import com.google.devtools.build.lib.remote.Scrubber.SpawnScrubber;
9091
import com.google.devtools.build.lib.remote.common.BulkTransferException;
9192
import com.google.devtools.build.lib.remote.common.OperationObserver;
9293
import com.google.devtools.build.lib.remote.common.OutputDigestMismatchException;
@@ -179,6 +180,8 @@ public class RemoteExecutionService {
179180

180181
@Nullable private final RemoteOutputChecker remoteOutputChecker;
181182

183+
@Nullable private final Scrubber scrubber;
184+
182185
public RemoteExecutionService(
183186
Executor executor,
184187
Reporter reporter,
@@ -210,6 +213,7 @@ public RemoteExecutionService(
210213
if (remoteOptions.remoteMerkleTreeCacheSize != 0) {
211214
merkleTreeCacheBuilder.maximumSize(remoteOptions.remoteMerkleTreeCacheSize);
212215
}
216+
this.scrubber = remoteOptions.scrubber;
213217
this.merkleTreeCache = merkleTreeCacheBuilder.build();
214218

215219
this.tempPathGenerator = tempPathGenerator;
@@ -219,12 +223,13 @@ public RemoteExecutionService(
219223
this.remoteOutputChecker = remoteOutputChecker;
220224
}
221225

222-
static Command buildCommand(
226+
private Command buildCommand(
223227
Collection<? extends ActionInput> outputs,
224228
List<String> arguments,
225229
ImmutableMap<String, String> env,
226230
@Nullable Platform platform,
227-
RemotePathResolver remotePathResolver) {
231+
RemotePathResolver remotePathResolver,
232+
@Nullable SpawnScrubber spawnScrubber) {
228233
Command.Builder command = Command.newBuilder();
229234
ArrayList<String> outputFiles = new ArrayList<>();
230235
ArrayList<String> outputDirectories = new ArrayList<>();
@@ -249,6 +254,9 @@ static Command buildCommand(
249254
command.setPlatform(platform);
250255
}
251256
for (String arg : arguments) {
257+
if (spawnScrubber != null) {
258+
arg = spawnScrubber.transformArgument(arg);
259+
}
252260
command.addArguments(decodeBytestringUtf8(arg));
253261
}
254262
// Sorting the environment pairs by variable name.
@@ -349,15 +357,18 @@ private SortedMap<PathFragment, ActionInput> buildOutputDirMap(Spawn spawn) {
349357
}
350358

351359
private MerkleTree buildInputMerkleTree(
352-
Spawn spawn, SpawnExecutionContext context, ToolSignature toolSignature)
360+
Spawn spawn,
361+
SpawnExecutionContext context,
362+
ToolSignature toolSignature,
363+
@Nullable SpawnScrubber spawnScrubber)
353364
throws IOException, ForbiddenActionInputException {
354365
// Add output directories to inputs so that they are created as empty directories by the
355366
// executor. The spec only requires the executor to create the parent directory of an output
356367
// directory, which differs from the behavior of both local and sandboxed execution.
357368
SortedMap<PathFragment, ActionInput> outputDirMap = buildOutputDirMap(spawn);
358369
boolean useMerkleTreeCache = remoteOptions.remoteMerkleTreeCache;
359-
if (toolSignature != null) {
360-
// Marking tool files is not yet supported in conjunction with the merkle tree cache.
370+
if (toolSignature != null || spawnScrubber != null) {
371+
// The Merkle tree cache is not yet compatible with scrubbing or marking tool files.
361372
useMerkleTreeCache = false;
362373
}
363374
if (useMerkleTreeCache) {
@@ -366,18 +377,22 @@ private MerkleTree buildInputMerkleTree(
366377
remotePathResolver.walkInputs(
367378
spawn,
368379
context,
369-
(Object nodeKey, InputWalker walker) -> {
370-
subMerkleTrees.add(
371-
buildMerkleTreeVisitor(
372-
nodeKey, walker, inputMetadataProvider, context.getPathResolver()));
373-
});
380+
(Object nodeKey, InputWalker walker) ->
381+
subMerkleTrees.add(
382+
buildMerkleTreeVisitor(
383+
nodeKey,
384+
walker,
385+
inputMetadataProvider,
386+
context.getPathResolver(),
387+
spawnScrubber)));
374388
if (!outputDirMap.isEmpty()) {
375389
subMerkleTrees.add(
376390
MerkleTree.build(
377391
outputDirMap,
378392
inputMetadataProvider,
379393
execRoot,
380394
context.getPathResolver(),
395+
/* spawnScrubber= */ null,
381396
digestUtil));
382397
}
383398
return MerkleTree.merge(subMerkleTrees, digestUtil);
@@ -399,6 +414,7 @@ private MerkleTree buildInputMerkleTree(
399414
context.getInputMetadataProvider(),
400415
execRoot,
401416
context.getPathResolver(),
417+
spawnScrubber,
402418
digestUtil);
403419
}
404420
}
@@ -407,7 +423,8 @@ private MerkleTree buildMerkleTreeVisitor(
407423
Object nodeKey,
408424
InputWalker walker,
409425
InputMetadataProvider inputMetadataProvider,
410-
ArtifactPathResolver artifactPathResolver)
426+
ArtifactPathResolver artifactPathResolver,
427+
@Nullable SpawnScrubber spawnScrubber)
411428
throws IOException, ForbiddenActionInputException {
412429
// Deduplicate concurrent computations for the same node. It's not possible to use
413430
// MerkleTreeCache#get(key, loader) because the loading computation may cause other nodes to be
@@ -419,7 +436,8 @@ private MerkleTree buildMerkleTreeVisitor(
419436
// No preexisting cache entry, so we must do the computation ourselves.
420437
try {
421438
freshFuture.complete(
422-
uncachedBuildMerkleTreeVisitor(walker, inputMetadataProvider, artifactPathResolver));
439+
uncachedBuildMerkleTreeVisitor(
440+
walker, inputMetadataProvider, artifactPathResolver, spawnScrubber));
423441
} catch (Exception e) {
424442
freshFuture.completeExceptionally(e);
425443
}
@@ -443,7 +461,8 @@ private MerkleTree buildMerkleTreeVisitor(
443461
public MerkleTree uncachedBuildMerkleTreeVisitor(
444462
InputWalker walker,
445463
InputMetadataProvider inputMetadataProvider,
446-
ArtifactPathResolver artifactPathResolver)
464+
ArtifactPathResolver artifactPathResolver,
465+
@Nullable SpawnScrubber scrubber)
447466
throws IOException, ForbiddenActionInputException {
448467
ConcurrentLinkedQueue<MerkleTree> subMerkleTrees = new ConcurrentLinkedQueue<>();
449468
subMerkleTrees.add(
@@ -452,18 +471,18 @@ public MerkleTree uncachedBuildMerkleTreeVisitor(
452471
inputMetadataProvider,
453472
execRoot,
454473
artifactPathResolver,
474+
scrubber,
455475
digestUtil));
456476
walker.visitNonLeaves(
457-
(Object subNodeKey, InputWalker subWalker) -> {
458-
subMerkleTrees.add(
459-
buildMerkleTreeVisitor(
460-
subNodeKey, subWalker, inputMetadataProvider, artifactPathResolver));
461-
});
477+
(Object subNodeKey, InputWalker subWalker) ->
478+
subMerkleTrees.add(
479+
buildMerkleTreeVisitor(
480+
subNodeKey, subWalker, inputMetadataProvider, artifactPathResolver, scrubber)));
462481
return MerkleTree.merge(subMerkleTrees, digestUtil);
463482
}
464483

465484
@Nullable
466-
private static ByteString buildSalt(Spawn spawn) {
485+
private static ByteString buildSalt(Spawn spawn, @Nullable SpawnScrubber spawnScrubber) {
467486
CacheSalt.Builder saltBuilder =
468487
CacheSalt.newBuilder().setMayBeExecutedRemotely(Spawns.mayBeExecutedRemotely(spawn));
469488

@@ -473,6 +492,11 @@ private static ByteString buildSalt(Spawn spawn) {
473492
saltBuilder.setWorkspace(workspace);
474493
}
475494

495+
if (spawnScrubber != null) {
496+
saltBuilder.setScrubSalt(
497+
CacheSalt.ScrubSalt.newBuilder().setSalt(spawnScrubber.getSalt()).build());
498+
}
499+
476500
return saltBuilder.build().toByteString();
477501
}
478502

@@ -508,7 +532,9 @@ public RemoteAction buildRemoteAction(Spawn spawn, SpawnExecutionContext context
508532
remoteActionBuildingSemaphore.acquire();
509533
try {
510534
ToolSignature toolSignature = getToolSignature(spawn, context);
511-
final MerkleTree merkleTree = buildInputMerkleTree(spawn, context, toolSignature);
535+
SpawnScrubber spawnScrubber = scrubber != null ? scrubber.forSpawn(spawn) : null;
536+
final MerkleTree merkleTree =
537+
buildInputMerkleTree(spawn, context, toolSignature, spawnScrubber);
512538

513539
// Get the remote platform properties.
514540
Platform platform;
@@ -526,7 +552,8 @@ public RemoteAction buildRemoteAction(Spawn spawn, SpawnExecutionContext context
526552
spawn.getArguments(),
527553
spawn.getEnvironment(),
528554
platform,
529-
remotePathResolver);
555+
remotePathResolver,
556+
spawnScrubber);
530557
Digest commandHash = digestUtil.compute(command);
531558
Action action =
532559
Utils.buildAction(
@@ -535,7 +562,7 @@ public RemoteAction buildRemoteAction(Spawn spawn, SpawnExecutionContext context
535562
platform,
536563
context.getTimeout(),
537564
Spawns.mayBeCachedRemotely(spawn),
538-
buildSalt(spawn));
565+
buildSalt(spawn, spawnScrubber));
539566

540567
ActionKey actionKey = digestUtil.computeActionKey(action);
541568

@@ -1414,7 +1441,8 @@ public void uploadInputsIfNotPresent(RemoteAction action, boolean force)
14141441
Spawn spawn = action.getSpawn();
14151442
SpawnExecutionContext context = action.getSpawnExecutionContext();
14161443
ToolSignature toolSignature = getToolSignature(spawn, context);
1417-
merkleTree = buildInputMerkleTree(spawn, context, toolSignature);
1444+
SpawnScrubber spawnScrubber = scrubber != null ? scrubber.forSpawn(spawn) : null;
1445+
merkleTree = buildInputMerkleTree(spawn, context, toolSignature, spawnScrubber);
14181446
}
14191447

14201448
remoteExecutionCache.ensureInputsPresent(

src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,14 @@ public void beforeCommand(CommandEnvironment env) throws AbruptExitException {
311311
FailureDetails.RemoteOptions.Code.EXECUTION_WITH_INVALID_CACHE);
312312
}
313313

314+
boolean enableScrubbing = remoteOptions.scrubber != null;
315+
if (enableScrubbing && enableRemoteExecution) {
316+
317+
throw createOptionsExitException(
318+
"Cannot combine remote cache key scrubbing with remote execution",
319+
FailureDetails.RemoteOptions.Code.EXECUTION_WITH_SCRUBBING);
320+
}
321+
314322
// TODO(bazel-team): Consider adding a warning or more validation if the remoteDownloadRegex is
315323
// used without Build without the Bytes.
316324
ImmutableList.Builder<Pattern> patternsToDownloadBuilder = ImmutableList.builder();

0 commit comments

Comments
 (0)