Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ public static Platform getPlatformProto(
? remoteOptions.getRemoteDefaultExecProperties()
: ImmutableSortedMap.of();

if (spawn.getExecutionPlatform() == null
if ((spawn.getExecutionPlatform() == null
|| (
spawn.getExecutionPlatform().execProperties().isEmpty()
&& spawn.getExecutionPlatform().remoteExecutionProperties().isEmpty()
))
Comment on lines +80 to +84
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Platform Condition Logic

Complex conditional logic checks platform nullability then accesses platform methods without null safety. If spawn.getExecutionPlatform() returns null, subsequent calls to execProperties() and remoteExecutionProperties() will cause NullPointerException. Logic should use null-safe evaluation pattern.

Standards
  • Algorithm-Correctness-Null-Safety
  • Logic-Verification-Conditional-Flow
  • Business-Rule-Error-Prevention

&& spawn.getCombinedExecProperties().isEmpty()
&& defaultExecProperties.isEmpty()
&& additionalProperties.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.google.devtools.build.lib.analysis.actions.FileWriteActionContext;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionContext;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.exec.ExecutionOptions;
import com.google.devtools.build.lib.exec.ModuleActionContextRegistry;
import com.google.devtools.build.lib.exec.SpawnCache;
Expand Down Expand Up @@ -90,5 +91,9 @@ public void registerSpawnStrategies(
for (Map.Entry<RegexFilter, List<String>> entry : options.strategyByRegexp) {
registryBuilder.addDescriptionFilter(entry.getKey(), entry.getValue());
}

for (Map.Entry<String, List<String>> strategy : options.allowedStrategiesByExecPlatform) {
registryBuilder.addExecPlatformFilter(Label.parseCanonicalUnchecked(strategy.getKey()), strategy.getValue());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unchecked Label Parsing

Using parseCanonicalUnchecked without validation allows malformed label strings to cause runtime exceptions or unexpected behavior. Malicious configuration could exploit this to cause denial of service or system instability through invalid label injection.

Standards
  • CWE-20
  • OWASP-A03
  • CWE-754

}
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/google/devtools/build/lib/exec/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/util:resource_converter",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/common/options",
"//src/main/java/com/google/devtools/build/lib/analysis:config/fragment_options",
"//third_party:guava",
],
)
Expand Down Expand Up @@ -364,6 +365,7 @@ java_library(
":remote_local_fallback_registry",
":spawn_strategy_policy",
"//src/main/java/com/google/devtools/build/lib/actions",
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/events",
"//src/main/java/com/google/devtools/build/lib/util",
"//src/main/java/com/google/devtools/build/lib/util:abrupt_exit_exception",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,23 @@ public class ExecutionOptions extends OptionsBase {
+ "the 'local' strategy, but reversing the order would run it with 'sandboxed'. ")
public List<Map.Entry<RegexFilter, List<String>>> strategyByRegexp;

@Option(
name = "allowed_strategies_by_exec_platform",
allowMultiple = true,
converter = Converters.StringToStringListConverter.class,
defaultValue = "null",
documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
effectTags = {OptionEffectTag.EXECUTION},
help =
"""
Filters spawn strategies by the execution platform.
Example: `--allowed_strategies_by_exec_platform=@platforms//host:host=local,sandboxed,worker`
to prevent actions configured for the host platform from being spawned remotely.
Example: `--allowed_strategies_by_exec_platform=//:linux_amd64=remote` to prevent actions
configured for a platform `//:linux_amd64` from being spawned locally.
""")
public List<Map.Entry<String, List<String>>> allowedStrategiesByExecPlatform;

@Option(
name = "materialize_param_files",
defaultValue = "false",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package com.google.devtools.build.lib.exec;

import com.google.devtools.build.lib.actions.ActionContext;
import com.google.devtools.build.lib.actions.Spawn;
import javax.annotation.Nullable;

/**
Expand All @@ -29,5 +30,5 @@ public interface RemoteLocalFallbackRegistry extends ActionContext {
* @return remote fallback strategy or {@code null} if none was registered
*/
@Nullable
AbstractSpawnStrategy getRemoteLocalFallbackStrategy();
AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
Expand All @@ -34,6 +35,7 @@
import com.google.devtools.build.lib.actions.SandboxedSpawnStrategy;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnStrategy;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.Reporter;
Expand Down Expand Up @@ -69,6 +71,7 @@ public final class SpawnStrategyRegistry

private final ImmutableListMultimap<String, SpawnStrategy> mnemonicToStrategies;
private final StrategyRegexFilter strategyRegexFilter;
private final StrategyPlatformFilter strategyPlatformFilter;
private final ImmutableList<? extends SpawnStrategy> defaultStrategies;
private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies;
private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies;
Expand All @@ -77,12 +80,14 @@ public final class SpawnStrategyRegistry
private SpawnStrategyRegistry(
ImmutableListMultimap<String, SpawnStrategy> mnemonicToStrategies,
StrategyRegexFilter strategyRegexFilter,
StrategyPlatformFilter strategyPlatformFilter,
ImmutableList<? extends SpawnStrategy> defaultStrategies,
ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies,
ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies,
@Nullable AbstractSpawnStrategy remoteLocalFallbackStrategy) {
this.mnemonicToStrategies = mnemonicToStrategies;
this.strategyRegexFilter = strategyRegexFilter;
this.strategyPlatformFilter = strategyPlatformFilter;
this.defaultStrategies = defaultStrategies;
this.mnemonicToRemoteDynamicStrategies = mnemonicToRemoteDynamicStrategies;
this.mnemonicToLocalDynamicStrategies = mnemonicToLocalDynamicStrategies;
Expand All @@ -105,7 +110,8 @@ private SpawnStrategyRegistry(
* using the given {@link Reporter}.
*/
public List<? extends SpawnStrategy> getStrategies(Spawn spawn, @Nullable EventHandler reporter) {
return getStrategies(spawn.getResourceOwner(), spawn.getMnemonic(), reporter);
return strategyPlatformFilter.getStrategies(
spawn, getStrategies(spawn.getResourceOwner(), spawn.getMnemonic(), reporter));
}

/**
Expand All @@ -116,6 +122,8 @@ public List<? extends SpawnStrategy> getStrategies(Spawn spawn, @Nullable EventH
*
* <p>If the reason for selecting the context is worth mentioning to the user, logs a message
* using the given {@link Reporter}.
*
* NOTE: This method is public for Blaze, getStrategies(Spawn, EventHandler) must be used in Bazel.
*/
public List<? extends SpawnStrategy> getStrategies(
ActionExecutionMetadata resourceOwner, String mnemonic, @Nullable EventHandler reporter) {
Expand Down Expand Up @@ -154,7 +162,7 @@ public ImmutableCollection<SandboxedSpawnStrategy> getDynamicSpawnActionContexts
? mnemonicToRemoteDynamicStrategies
: mnemonicToLocalDynamicStrategies;
if (mnemonicToDynamicStrategies.containsKey(spawn.getMnemonic())) {
return mnemonicToDynamicStrategies.get(spawn.getMnemonic());
return strategyPlatformFilter.getStrategies(spawn, mnemonicToDynamicStrategies.get(spawn.getMnemonic()));
}
if (mnemonicToDynamicStrategies.containsKey("")) {
return mnemonicToDynamicStrategies.get("");
Expand All @@ -164,8 +172,9 @@ public ImmutableCollection<SandboxedSpawnStrategy> getDynamicSpawnActionContexts

@Nullable
@Override
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy() {
return remoteLocalFallbackStrategy;
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
return strategyPlatformFilter.getStrategies(spawn, Lists.newArrayList(remoteLocalFallbackStrategy))
.getFirst();
Comment on lines +175 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NoSuchElementException risk in remote fallback strategy

If remoteLocalFallbackStrategy is null, Lists.newArrayList will create an empty list, and calling getFirst() on an empty list will throw NoSuchElementException, causing runtime failures.

Suggested change
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
return strategyPlatformFilter.getStrategies(spawn, Lists.newArrayList(remoteLocalFallbackStrategy))
.getFirst();
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
if (remoteLocalFallbackStrategy == null) {
return null;
}
return strategyPlatformFilter.getStrategies(spawn, Lists.newArrayList(remoteLocalFallbackStrategy))
.getFirst();
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Defensive-Programming

Comment on lines +175 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential NPE Risk

The method returns the first element from filtered strategies without checking if the list is empty. If all strategies are filtered out, getFirst() will throw a NoSuchElementException. This creates a reliability risk when no strategies match the platform filter criteria.

  public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
    if (remoteLocalFallbackStrategy == null) {
      return null;
    }
    List<AbstractSpawnStrategy> filteredStrategies = strategyPlatformFilter.getStrategies(
        spawn, Lists.newArrayList(remoteLocalFallbackStrategy));
    return filteredStrategies.isEmpty() ? null : filteredStrategies.get(0);
Commitable Suggestion
Suggested change
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
return strategyPlatformFilter.getStrategies(spawn, Lists.newArrayList(remoteLocalFallbackStrategy))
.getFirst();
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
if (remoteLocalFallbackStrategy == null) {
return null;
}
List<AbstractSpawnStrategy> filteredStrategies = strategyPlatformFilter.getStrategies(
spawn, Lists.newArrayList(remoteLocalFallbackStrategy));
return filteredStrategies.isEmpty() ? null : filteredStrategies.get(0);
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Error-Handling

Comment on lines +175 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential NullPointerException

The method returns the first element from filtered strategies without checking if remoteLocalFallbackStrategy is null or if the filtered list is empty. This could lead to NullPointerException when remoteLocalFallbackStrategy is null or when all strategies are filtered out.

  public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
    if (remoteLocalFallbackStrategy == null) {
      return null;
    }
    List<AbstractSpawnStrategy> filteredStrategies = strategyPlatformFilter.getStrategies(
        spawn, Lists.newArrayList(remoteLocalFallbackStrategy));
    return filteredStrategies.isEmpty() ? null : filteredStrategies.get(0);
Commitable Suggestion
Suggested change
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
return strategyPlatformFilter.getStrategies(spawn, Lists.newArrayList(remoteLocalFallbackStrategy))
.getFirst();
public AbstractSpawnStrategy getRemoteLocalFallbackStrategy(Spawn spawn) {
if (remoteLocalFallbackStrategy == null) {
return null;
}
List<AbstractSpawnStrategy> filteredStrategies = strategyPlatformFilter.getStrategies(
spawn, Lists.newArrayList(remoteLocalFallbackStrategy));
return filteredStrategies.isEmpty() ? null : filteredStrategies.get(0);
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

Comment on lines +176 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resource Leak Risk

Potential null pointer exception when remoteLocalFallbackStrategy is null. The method creates a new ArrayList containing potentially null strategy without validation, then calls getFirst() which could fail if filtering returns empty list. This creates unreliable fallback behavior that could cause service degradation during remote execution failures.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Postcondition-Validation

Comment on lines +176 to +177
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strategy Collection Logic

Method calls getFirst() on filtered strategy list without null or empty validation. If strategyPlatformFilter returns empty list or remoteLocalFallbackStrategy is null, getFirst() will throw NoSuchElementException. Business logic requires fallback strategy availability guarantee.

Standards
  • Algorithm-Correctness-Collection-Safety
  • Business-Rule-Fallback-Logic
  • Logic-Verification-Error-Handling

}

/**
Expand Down Expand Up @@ -203,9 +212,16 @@ void logSpawnStrategies() {
strategyRegexFilter.getFilterToStrategies().asMap().entrySet()) {
Collection<SpawnStrategy> value = entry.getValue();
logger.atInfo().log(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inappropriate Logging Level

The code uses INFO level for logging filter platform to strategy implementations. According to repository guidelines, a more appropriate logging level should be considered. This may lead to log pollution in production environments where this level of detail isn't necessary.

Standards
  • Repo-Guideline-Consider using a more appropriate logging level

"FilterToStrategyImplementations: \"%s\" = [%s]",
"FilterDescriptionToStrategyImplementations: \"%s\" = [%s]",
entry.getKey(), toImplementationNames(value));
Comment on lines +215 to 216
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inappropriate Logging Level

The code uses logger.atInfo() for logging filter strategy implementations. According to repository guidelines, a more appropriate logging level should be considered for this type of diagnostic information, likely debug level would be more suitable for detailed implementation mappings.

Standards
  • Repo-Guideline-Consider using a more appropriate logging level

}
for (Map.Entry<Label, ImmutableList<SpawnStrategy>> entry :
strategyPlatformFilter.getFilterToStrategies().entrySet()) {
Collection<SpawnStrategy> value = entry.getValue();
logger.atInfo().log(
"FilterPlatformToStrategyImplementations: \"%s\" = [%s]",
entry.getKey().getCanonicalName(), toImplementationNames(value));
Comment on lines +221 to +223
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inappropriate Logging Level

Repository guideline violation: using INFO level for platform filtering logging may not be appropriate. This implementation logs platform-to-strategy mappings at INFO level which could create excessive log noise in production environments. Consider using DEBUG or TRACE level for detailed implementation mappings to improve system observability without overwhelming logs.

Standards
  • Repo-Guideline-Consider using a more appropriate logging level
  • ISO-IEC-25010-Reliability-Maturity
  • SRE-Observability

Comment on lines +221 to +223
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inappropriate Logging Level

Repository guideline violation: using INFO level for platform filter logging may not be appropriate. Platform filtering configuration details might be better suited for DEBUG level to reduce noise in production logs while maintaining debugging capability.

Standards
  • Repo-Guideline-Consider using a more appropriate logging level
  • Clean-Code-Comments
  • Maintainability-Quality-Observability

}

logger.atInfo().log(
"DefaultStrategyImplementations: [%s]", toImplementationNames(defaultStrategies));
Expand Down Expand Up @@ -287,6 +303,7 @@ public static final class Builder {
private final HashMap<String, List<String>> mnemonicToRemoteDynamicIdentifiers =
new HashMap<>();
private final HashMap<String, List<String>> mnemonicToLocalDynamicIdentifiers = new HashMap<>();
private final HashMap<Label, List<String>> execPlatformFilters = new HashMap<>();

@Nullable private String remoteLocalFallbackStrategyIdentifier;

Expand Down Expand Up @@ -314,6 +331,12 @@ public Builder addDescriptionFilter(RegexFilter filter, List<String> identifiers
return this;
}

@CanIgnoreReturnValue
public Builder addExecPlatformFilter(Label execPlatform, List<String> identifiers) {
this.execPlatformFilters.put(execPlatform, identifiers);
return this;
}

/**
* Adds a filter limiting any spawn whose {@linkplain Spawn#getMnemonic() mnemonic}
* (case-sensitively) matches the given mnemonic to only use strategies with the given
Expand Down Expand Up @@ -444,6 +467,14 @@ public SpawnStrategyRegistry build() throws AbruptExitException {
}
}

ImmutableMap.Builder<Label, ImmutableList<SpawnStrategy>> platformToStrategies = ImmutableMap.builder();
for (Map.Entry<Label, List<String>> entry : execPlatformFilters.entrySet()) {
Label platform = entry.getKey();
platformToStrategies.put(
platform,
strategyMapper.toStrategies(entry.getValue(), "platform " + platform.getCanonicalName()));
}

ImmutableListMultimap.Builder<String, SpawnStrategy> mnemonicToStrategies =
new ImmutableListMultimap.Builder<>();
for (Map.Entry<String, List<String>> entry : mnemonicToIdentifiers.entrySet()) {
Expand Down Expand Up @@ -514,6 +545,7 @@ public SpawnStrategyRegistry build() throws AbruptExitException {
mnemonicToStrategies.build(),
new StrategyRegexFilter(
strategyMapper, strategyPolicy, filterToIdentifiers, filterToStrategies),
new StrategyPlatformFilter(strategyMapper, platformToStrategies.build()),
defaultStrategies,
mnemonicToRemoteStrategies.build(),
mnemonicToLocalStrategies.build(),
Expand Down Expand Up @@ -595,6 +627,46 @@ public String toString() {
}
}

private static class StrategyPlatformFilter {
private final StrategyMapper strategyMapper;
private final ImmutableMap<Label, ImmutableList<SpawnStrategy>> platformToStrategies;

private StrategyPlatformFilter(
StrategyMapper strategyMapper,
ImmutableMap<Label, ImmutableList<SpawnStrategy>> platformToStrategies) {
this.strategyMapper = strategyMapper;
this.platformToStrategies = platformToStrategies;
}

public <T extends SpawnStrategy> List<T> getStrategies(
Spawn spawn, List<T> candidateStrategies) {
var platformLabel = spawn.getExecutionPlatformLabel();
Preconditions.checkNotNull(platformLabel, "Attempting to spawn action without an execution platform.");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null pointer risk in platform label validation

The null check for platformLabel will throw NPE when a spawn has no execution platform. This contradicts the existing code in getPlatformProto that handles null execution platforms gracefully.

Suggested change
Preconditions.checkNotNull(platformLabel, "Attempting to spawn action without an execution platform.");
if (platformLabel == null) {
return candidateStrategies;
}
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • SRE-Graceful-Degradation

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Null Check

Code directly calls checkNotNull without first checking if platformLabel is null. This violates the repository guideline to check for null before using Preconditions.checkNotNull. This could lead to unnecessary exceptions when a simple null check could handle the case more gracefully.

      if (platformLabel == null) {
        return candidateStrategies;
      }
Commitable Suggestion
Suggested change
Preconditions.checkNotNull(platformLabel, "Attempting to spawn action without an execution platform.");
if (platformLabel == null) {
return candidateStrategies;
}
Standards
  • Repo-Guideline-check weather null check condition applied for platformlabel before Preconditions.checkNotNull

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Null Check

Null check is performed using Preconditions.checkNotNull without first checking if platformLabel is null. This violates the repository guideline requiring a null check before using Preconditions.checkNotNull. This could lead to unnecessary exceptions when a simple null check could handle the case more gracefully.

      if (platformLabel == null) {
        throw new NullPointerException("Attempting to spawn action without an execution platform.");
      }
Commitable Suggestion
Suggested change
Preconditions.checkNotNull(platformLabel, "Attempting to spawn action without an execution platform.");
if (platformLabel == null) {
throw new NullPointerException("Attempting to spawn action without an execution platform.");
}
Standards
  • Repo-Guideline-check weather null check condition applied for platformlabel before Preconditions.checkNotNull

Comment on lines +643 to +644
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Null Check

Repository guideline violation: null check condition should be applied before Preconditions.checkNotNull. The current implementation retrieves platformLabel and immediately passes it to checkNotNull without validation, which could cause unclear error messages if the value is null.

      if (platformLabel == null) {
        throw new NullPointerException("Attempting to spawn action without an execution platform.");
      }
Commitable Suggestion
Suggested change
var platformLabel = spawn.getExecutionPlatformLabel();
Preconditions.checkNotNull(platformLabel, "Attempting to spawn action without an execution platform.");
if (platformLabel == null) {
throw new NullPointerException("Attempting to spawn action without an execution platform.");
}
Standards
  • Repo-Guideline-check weather null check condition applied for platformlabel before Preconditions.checkNotNull
  • CWE-476
  • OWASP-A06


var allowedStrategies = platformToStrategies.get(platformLabel);
Comment on lines +638 to +646
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential N+1 Performance Issue in Platform Filter Strategy Selection

The current implementation iterates through all candidate strategies for each spawn, checking if each strategy is in the allowed strategies list. This creates an O(n*m) operation where n is the number of candidate strategies and m is the number of allowed strategies. For platforms with many strategies and frequent spawns, this could create unnecessary CPU overhead. Additionally, the linear search pattern can be inefficient for large strategy collections.

      var allowedStrategies = platformToStrategies.get(platformLabel);
      if (allowedStrategies != null) {
        // Convert to HashSet for O(1) lookups instead of O(n) contains() operations
        Set<SpawnStrategy> allowedStrategiesSet = new HashSet<>(allowedStrategies);
        List<T> filteredStrategies = new ArrayList<>();
        for (var strategy : candidateStrategies) {
          if (allowedStrategiesSet.contains(strategy)) {
            filteredStrategies.add(strategy);
          }
        }
        // If filtering resulted in an empty list, return original candidates
        // to ensure at least one strategy is available
        return filteredStrategies.isEmpty() ? candidateStrategies : filteredStrategies;
      }
References

Standard: Google's Core Performance Principles - Hot Path Optimization

if (allowedStrategies != null) {
List<T> filteredStrategies = new ArrayList<>();
for (var strategy : candidateStrategies) {
if (allowedStrategies.contains(strategy)) {
filteredStrategies.add(strategy);
}
}
return filteredStrategies;
}

return candidateStrategies;
}

public <T extends SpawnStrategy> ImmutableCollection<T> getStrategies(
Spawn spawn, ImmutableCollection<T> candidateStrategies) {
return ImmutableList.copyOf(getStrategies(spawn, Lists.newCopyOnWriteArrayList(candidateStrategies)));
}

ImmutableMap<Label, ImmutableList<SpawnStrategy>> getFilterToStrategies() {
return platformToStrategies;
}
}

/* Maps the strategy identifier (e.g. "local", "worker"..) to the real strategy. */
private static class StrategyMapper {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,12 @@ public List<? extends SpawnStrategy> resolve(
if (fallbackStrategies.isEmpty()) {
String message =
String.format(
"%s spawn cannot be executed with any of the available strategies: %s. Your"
+ " --spawn_strategy, --genrule_strategy and/or --strategy flags are probably"
+ " too strict. Visit https://github.com/bazelbuild/bazel/issues/7480 for"
+ " advice",
"""
%s spawn cannot be executed with any of the available strategies: %s. Your \
--spawn_strategy, --genrule_strategy, strategy and/or \
--allowed_strategies_by_exec_platform flags are probably too strict. \
Visit https://github.com/bazelbuild/bazel/issues/7480 for advice.
""",
spawn.getMnemonic(), strategies);
throw new UserExecException(
FailureDetail.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ private SpawnResult execLocally(Spawn spawn, SpawnExecutionContext context)
context.getContext(RemoteLocalFallbackRegistry.class);
checkNotNull(localFallbackRegistry, "Expected a RemoteLocalFallbackRegistry to be registered");
AbstractSpawnStrategy remoteLocalFallbackStrategy =
localFallbackRegistry.getRemoteLocalFallbackStrategy();
localFallbackRegistry.getRemoteLocalFallbackStrategy(spawn);
checkNotNull(
remoteLocalFallbackStrategy,
"A remote local fallback strategy must be set if using remote fallback.");
Expand Down
1 change: 1 addition & 0 deletions src/test/java/com/google/devtools/build/lib/exec/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/analysis:config/invalid_configuration_exception",
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
"//src/main/java/com/google/devtools/build/lib/analysis:server_directories",
"//src/main/java/com/google/devtools/build/lib/analysis/platform",
"//src/main/java/com/google/devtools/build/lib/bazel/rules/python",
"//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
"//src/main/java/com/google/devtools/build/lib/clock",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnResult;
import com.google.devtools.build.lib.actions.SpawnStrategy;
import com.google.devtools.build.lib.analysis.platform.PlatformInfo;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.events.Event;
Expand Down Expand Up @@ -300,6 +302,24 @@ public void testDuplicatedDescriptionFilter() throws Exception {
.containsExactly(strategy2);
}

@Test
public void testPlatformFilter() throws Exception {
NoopStrategy strategy1 = new NoopStrategy("1");
NoopStrategy strategy2 = new NoopStrategy("2");
SpawnStrategyRegistry strategyRegistry =
SpawnStrategyRegistry.builder()
.registerStrategy(strategy1, "foo")
.registerStrategy(strategy2, "bar")
.addExecPlatformFilter(PlatformInfo.EMPTY_PLATFORM_INFO.label(), ImmutableList.of("foo"))
.build();

assertThat(
strategyRegistry.getStrategies(
createSpawnWithMnemonicAndDescription("", ""),
SpawnStrategyRegistryTest::noopEventHandler))
.containsExactly(strategy1);
}

@Test
public void testMultipleDefaultStrategies() throws Exception {
NoopStrategy strategy1 = new NoopStrategy("1");
Expand Down Expand Up @@ -537,7 +557,7 @@ public void testRemoteLocalFallback() throws Exception {
.setRemoteLocalFallbackStrategyIdentifier("bar")
.build();

assertThat(strategyRegistry.getRemoteLocalFallbackStrategy()).isEqualTo(strategy2);
assertThat(strategyRegistry.getRemoteLocalFallbackStrategy(createSpawnWithMnemonicAndDescription("", ""))).isEqualTo(strategy2);
}

@Test
Expand All @@ -561,7 +581,7 @@ public void testRemoteLocalFallbackNotRegistered() throws Exception {
SpawnStrategyRegistry strategyRegistry =
SpawnStrategyRegistry.builder().registerStrategy(strategy1, "foo").build();

assertThat(strategyRegistry.getRemoteLocalFallbackStrategy()).isNull();
assertThat(strategyRegistry.getRemoteLocalFallbackStrategy(createSpawnWithMnemonicAndDescription("", ""))).isNull();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ private FakeOwner(
}

public FakeOwner(String mnemonic, String progressMessage, String ownerLabel) {
this(mnemonic, progressMessage, checkNotNull(ownerLabel), null);
this(
mnemonic,
progressMessage,
checkNotNull(ownerLabel),
PlatformInfo.EMPTY_PLATFORM_INFO);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ private FakeSpawnExecutionContext getSpawnContext(Spawn spawn) {
AbstractSpawnStrategy fakeLocalStrategy =
new AbstractSpawnStrategy(execRoot, localRunner, new ExecutionOptions()) {};
ClassToInstanceMap<ActionContext> actionContextRegistry =
ImmutableClassToInstanceMap.of(RemoteLocalFallbackRegistry.class, () -> fakeLocalStrategy);
ImmutableClassToInstanceMap.of(RemoteLocalFallbackRegistry.class, (_spawn) -> fakeLocalStrategy);

var actionInputFetcher =
new RemoteActionInputFetcher(
Expand Down