Skip to content

Commit e6cce76

Browse files
alexjskicopybara-github
authored andcommitted
Fix false sharing in action cache due to incorrect key computation for custom
Starlark command lines. Actions constructing arguments in Starlark can define them as a lazy evaluation, which will be run when we are about to execute the action, using `Args.add_all`. `Args.add_all` offers a `expand_directories` feature which causes all of the directories in the args list to be replaced with the files in the directory at the time of evaluation. When this option is used in conjunction with `map_each`, the behavior is to first expand the directory and than run the provided function on each of the elements of the expanded array. `ArtifactExpander`, used to expand the directories, is not available before execution phase. Given `Action.getKey` can be called before that (and is for instance for shared actions detection), we currently compute the fingerprint without expanding directories. In fact, current implementation, calls the provided function on the directory itself rather than expanded list of files in it. As a result of that, if we have 2 functions which return the same value for the directory, but differ for the files in it, the key will not denote them as different. That leads to false sharing of actions through the action cache, which is based on action fingerprints. Action cache checks happen during execution phase, when we technically have the ability to expand the directories. Expand the fingerprinting function to take advantage of `ArtifactExpander` when it is provided to produce the key based on the result of actual function execution. Rework the `ActionExecutionFunction` to provide `ArtifactExpander` to logic computing keys for action caches. PiperOrigin-RevId: 323090803
1 parent 636fb5e commit e6cce76

15 files changed

+494
-96
lines changed

src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.common.collect.ImmutableList;
2020
import com.google.common.collect.ImmutableMap;
2121
import com.google.common.collect.ImmutableSet;
22+
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
2223
import com.google.devtools.build.lib.actions.cache.ActionCache;
2324
import com.google.devtools.build.lib.actions.cache.DigestUtils;
2425
import com.google.devtools.build.lib.actions.cache.MetadataHandler;
@@ -249,6 +250,7 @@ public Token getTokenIfNeedToExecute(
249250
Map<String, String> clientEnv,
250251
EventHandler handler,
251252
MetadataHandler metadataHandler,
253+
ArtifactExpander artifactExpander,
252254
Map<String, String> remoteDefaultPlatformProperties) {
253255
// TODO(bazel-team): (2010) For RunfilesAction/SymlinkAction and similar actions that
254256
// produce only symlinks we should not check whether inputs are valid at all - all that matters
@@ -292,6 +294,7 @@ public Token getTokenIfNeedToExecute(
292294
entry,
293295
handler,
294296
metadataHandler,
297+
artifactExpander,
295298
actionInputs,
296299
clientEnv,
297300
remoteDefaultPlatformProperties)) {
@@ -307,11 +310,12 @@ public Token getTokenIfNeedToExecute(
307310
return null;
308311
}
309312

310-
protected boolean mustExecute(
313+
private boolean mustExecute(
311314
Action action,
312315
@Nullable ActionCache.Entry entry,
313316
EventHandler handler,
314317
MetadataHandler metadataHandler,
318+
ArtifactExpander artifactExpander,
315319
NestedSet<Artifact> actionInputs,
316320
Map<String, String> clientEnv,
317321
Map<String, String> remoteDefaultPlatformProperties) {
@@ -336,9 +340,7 @@ protected boolean mustExecute(
336340
reportChanged(handler, action);
337341
actionCache.accountMiss(MissReason.DIFFERENT_FILES);
338342
return true;
339-
} else if (!entry
340-
.getActionKey()
341-
.equals(action.getKey(actionKeyContext, /*artifactExpander=*/ null))) {
343+
} else if (!entry.getActionKey().equals(action.getKey(actionKeyContext, artifactExpander))) {
342344
reportCommand(handler, action);
343345
actionCache.accountMiss(MissReason.DIFFERENT_ACTION_KEY);
344346
return true;
@@ -382,6 +384,7 @@ public void updateActionCache(
382384
Action action,
383385
Token token,
384386
MetadataHandler metadataHandler,
387+
ArtifactExpander artifactExpander,
385388
Map<String, String> clientEnv,
386389
Map<String, String> remoteDefaultPlatformProperties)
387390
throws IOException {
@@ -397,7 +400,7 @@ public void updateActionCache(
397400
computeUsedEnv(action, clientEnv, remoteDefaultPlatformProperties);
398401
ActionCache.Entry entry =
399402
new ActionCache.Entry(
400-
action.getKey(actionKeyContext, /*artifactExpander=*/ null),
403+
action.getKey(actionKeyContext, artifactExpander),
401404
usedEnvironment,
402405
action.discoversInputs());
403406
for (Artifact output : action.getOutputs()) {

src/main/java/com/google/devtools/build/lib/actions/ActionKeyCacher.java

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,44 @@ public abstract class ActionKeyCacher implements ActionAnalysisMetadata {
2828
@Override
2929
public final String getKey(
3030
ActionKeyContext actionKeyContext, @Nullable ArtifactExpander artifactExpander) {
31+
// Only cache the key when it is given all necessary information to compute a correct key.
32+
// Practically, most of the benefit of the cache comes from execution, which does provide the
33+
// artifactExpander.
34+
if (artifactExpander == null) {
35+
return computeActionKey(actionKeyContext, null);
36+
}
37+
3138
if (cachedKey == null) {
3239
synchronized (this) {
3340
if (cachedKey == null) {
34-
try {
35-
Fingerprint fp = new Fingerprint();
36-
// TODO(b/153904017): Make use of the provided artifactExpander and only cache if
37-
// present.
38-
computeKey(actionKeyContext, /*artifactExpander=*/ null, fp);
39-
40-
// Add a bool indicating whether the execution platform was set.
41-
fp.addBoolean(getExecutionPlatform() != null);
42-
if (getExecutionPlatform() != null) {
43-
// Add the execution platform information.
44-
getExecutionPlatform().addTo(fp);
45-
}
46-
47-
fp.addStringMap(getExecProperties());
48-
49-
// Compute the actual key and store it.
50-
cachedKey = fp.hexDigestAndReset();
51-
} catch (CommandLineExpansionException e) {
52-
cachedKey = KEY_ERROR;
53-
}
41+
cachedKey = computeActionKey(actionKeyContext, artifactExpander);
5442
}
5543
}
5644
}
5745
return cachedKey;
5846
}
5947

48+
private String computeActionKey(
49+
ActionKeyContext actionKeyContext, @Nullable ArtifactExpander artifactExpander) {
50+
try {
51+
Fingerprint fp = new Fingerprint();
52+
computeKey(actionKeyContext, artifactExpander, fp);
53+
54+
// Add a bool indicating whether the execution platform was set.
55+
fp.addBoolean(getExecutionPlatform() != null);
56+
if (getExecutionPlatform() != null) {
57+
// Add the execution platform information.
58+
getExecutionPlatform().addTo(fp);
59+
}
60+
61+
fp.addStringMap(getExecProperties());
62+
// Compute the actual key and store it.
63+
return fp.hexDigestAndReset();
64+
} catch (CommandLineExpansionException e) {
65+
return KEY_ERROR;
66+
}
67+
}
68+
6069
/**
6170
* See the javadoc for {@link Action} and {@link ActionAnalysisMetadata#getKey} for the contract
6271
* of this method.

src/main/java/com/google/devtools/build/lib/actions/CommandLine.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
2323
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
2424
import com.google.devtools.build.lib.util.Fingerprint;
25+
import javax.annotation.Nullable;
2526

2627
/** A representation of a list of arguments. */
2728
public abstract class CommandLine {
@@ -52,7 +53,18 @@ public Iterable<String> arguments(ArtifactExpander artifactExpander)
5253
return arguments();
5354
}
5455

55-
public void addToFingerprint(ActionKeyContext actionKeyContext, Fingerprint fingerprint)
56+
/**
57+
* Adds the command line to the provided {@link Fingerprint}.
58+
*
59+
* <p>Some of the implementations may require the to expand provided directory in order to produce
60+
* a unique key. Consequently, the result of calling this function can be different depending on
61+
* whether the {@link ArtifactExpander} is provided. Moreover, without it, the produced key may
62+
* not always be unique.
63+
*/
64+
public void addToFingerprint(
65+
ActionKeyContext actionKeyContext,
66+
@Nullable ArtifactExpander artifactExpander,
67+
Fingerprint fingerprint)
5668
throws CommandLineExpansionException {
5769
for (String s : arguments()) {
5870
fingerprint.addString(s);

src/main/java/com/google/devtools/build/lib/actions/CommandLines.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,19 +180,22 @@ ExpandedCommandLines expand(
180180
return new ExpandedCommandLines(arguments.build(), paramFiles);
181181
}
182182

183-
public void addToFingerprint(ActionKeyContext actionKeyContext, Fingerprint fingerprint)
183+
public void addToFingerprint(
184+
ActionKeyContext actionKeyContext,
185+
@Nullable ArtifactExpander artifactExpander,
186+
Fingerprint fingerprint)
184187
throws CommandLineExpansionException {
185188
// Optimize for simple case of single command line
186189
if (commandLines instanceof CommandLine) {
187190
CommandLine commandLine = (CommandLine) commandLines;
188-
commandLine.addToFingerprint(actionKeyContext, fingerprint);
191+
commandLine.addToFingerprint(actionKeyContext, artifactExpander, fingerprint);
189192
return;
190193
}
191194
List<CommandLineAndParamFileInfo> commandLines = getCommandLines();
192195
for (CommandLineAndParamFileInfo pair : commandLines) {
193196
CommandLine commandLine = pair.commandLine;
194197
ParamFileInfo paramFileInfo = pair.paramFileInfo;
195-
commandLine.addToFingerprint(actionKeyContext, fingerprint);
198+
commandLine.addToFingerprint(actionKeyContext, artifactExpander, fingerprint);
196199
if (paramFileInfo != null) {
197200
addParamFileInfoToFingerprint(paramFileInfo, fingerprint);
198201
}

src/main/java/com/google/devtools/build/lib/analysis/actions/CustomCommandLine.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,10 @@ private Object substituteTreeFileArtifactArgvFragment(Object arg) {
12881288

12891289
@Override
12901290
@SuppressWarnings("unchecked")
1291-
public void addToFingerprint(ActionKeyContext actionKeyContext, Fingerprint fingerprint) {
1291+
public void addToFingerprint(
1292+
ActionKeyContext actionKeyContext,
1293+
@Nullable ArtifactExpander artifactExpander,
1294+
Fingerprint fingerprint) {
12921295
int count = arguments.size();
12931296
for (int i = 0; i < count; ) {
12941297
Object arg = arguments.get(i++);

src/main/java/com/google/devtools/build/lib/analysis/actions/ParameterFileWriteAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,6 @@ protected void computeKey(
182182
fp.addString(GUID);
183183
fp.addString(String.valueOf(makeExecutable));
184184
fp.addString(type.toString());
185-
commandLine.addToFingerprint(actionKeyContext, fp);
185+
commandLine.addToFingerprint(actionKeyContext, artifactExpander, fp);
186186
}
187187
}

src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ protected void computeKey(
441441
Fingerprint fp)
442442
throws CommandLineExpansionException {
443443
fp.addString(GUID);
444-
commandLines.addToFingerprint(actionKeyContext, fp);
444+
commandLines.addToFingerprint(actionKeyContext, artifactExpander, fp);
445445
fp.addString(getMnemonic());
446446
// We don't need the toolManifests here, because they are a subset of the inputManifests by
447447
// definition and the output of an action shouldn't change whether something is considered a

0 commit comments

Comments
 (0)