Skip to content

Commit

Permalink
Make --noallow_discard_analysis_cache work when used twice in a seque…
Browse files Browse the repository at this point in the history
…nce.

This didn't work at HEAD because the check there compared the flags in the new configuration with the flags of the old configuration, but overwrote the old configuration with the new one, so running a command that discarded the analysis cache was allowed the second time.

The fix is not as trivial as moving checking the value before setting the new configuration because creating the new configuration entails a Skyframe evaluation, which is a waste if we end up erroring out. So the check is now done by checking the build options of the old configuration and the new build options, which doesn't require creating the new configuration first.

Fixes #23491.

RELNOTES: None.
PiperOrigin-RevId: 675555324
Change-Id: Ib402b699ed9ba9f04542896a40ff1180351471db
  • Loading branch information
lberki authored and copybara-github committed Sep 17, 2024
1 parent 94b72e4 commit dc61274
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,19 @@ public AnalysisResult update(

// Prepare the analysis phase
BuildConfigurationValue topLevelConfig;
boolean shouldDiscardAnalysisCache;

// Configuration creation.
// TODO(gregce): Consider dropping this phase and passing on-the-fly target / exec configs as
// needed. This requires cleaning up the invalidation in SkyframeBuildView.setConfigurations.
try (SilentCloseable c = Profiler.instance().profile("createConfigurations")) {
shouldDiscardAnalysisCache =
skyframeBuildView.shouldDiscardAnalysisCache(
eventHandler,
targetOptions,
viewOptions.maxConfigChangesToShow,
viewOptions.allowAnalysisCacheDiscards,
additionalConfigurationChangeEvent);
topLevelConfig = skyframeExecutor.createConfiguration(eventHandler, targetOptions, keepGoing);
SkyfocusState skyfocusState = skyframeExecutor.getSkyfocusState();
if (skyfocusState.enabled()) {
Expand Down Expand Up @@ -309,12 +318,7 @@ public AnalysisResult update(
buildConfigurationsCreatedCallback.run(topLevelConfig);
}

skyframeBuildView.setConfiguration(
eventHandler,
topLevelConfig,
viewOptions.maxConfigChangesToShow,
viewOptions.allowAnalysisCacheDiscards,
additionalConfigurationChangeEvent);
skyframeBuildView.setConfiguration(topLevelConfig, targetOptions, shouldDiscardAnalysisCache);

eventBus.post(new MakeEnvironmentEvent(topLevelConfig.getMakeEnvironment()));
eventBus.post(topLevelConfig.toBuildEvent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.analysis.config.AdditionalConfigurationChangeEvent;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.ConfigConditions;
import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
import com.google.devtools.build.lib.analysis.config.OptionsDiff;
Expand Down Expand Up @@ -161,6 +162,7 @@ public final class SkyframeBuildView {

// Null until the build configuration is set.
@Nullable private BuildConfigurationValue configuration;
@Nullable private BuildOptions originalConfigurationOptions;

/**
* If the last build was executed with {@code Options#discard_analysis_cache} and we are not
Expand Down Expand Up @@ -229,16 +231,8 @@ public ImmutableMap<String, Integer> getEvaluatedActionCountsByMnemonic() {
*/
@Nullable
private String describeConfigurationDifference(
BuildConfigurationValue configuration, int maxDifferencesToShow) {
if (this.configuration == null) {
return null;
}
if (configuration.equals(this.configuration)) {
return null;
}

OptionsDiff diff =
OptionsDiff.diff(this.configuration.getOptions(), configuration.getOptions());
BuildOptions oldOptions, BuildOptions newOptions, int maxDifferencesToShow) {
OptionsDiff diff = OptionsDiff.diff(oldOptions, newOptions);

ImmutableSet<OptionDefinition> nativeCacheInvalidatingDifferences =
getNativeCacheInvalidatingDifferences(configuration, diff);
Expand Down Expand Up @@ -299,51 +293,80 @@ private ImmutableSet<OptionDefinition> getNativeCacheInvalidatingDifferences(
.collect(toImmutableSet());
}

/** Sets the configuration. Not thread-safe. DO NOT CALL except from tests! */
@VisibleForTesting
public void setConfiguration(
/**
* Returns whether the analysis results from previous invocations should be discarded or report an
* error if it should be, but it's disallowed.
*
* <p>This should happen when the top-level configuration has changed or if the previous
* invocation decided that this should happen. Either way, this method also emits a message
* informing the user about this decision.
*/
public boolean shouldDiscardAnalysisCache(
EventHandler eventHandler,
BuildConfigurationValue configuration,
BuildOptions newOptions,
int maxDifferencesToShow,
boolean allowAnalysisCacheDiscards,
Optional<AdditionalConfigurationChangeEvent> additionalConfigurationChangeEvent)
throws InvalidConfigurationException {
if (this.configuration == null) {
return false;
}

if (skyframeAnalysisWasDiscarded) {
logger.atInfo().log("Discarding analysis cache because the previous invocation told us to");
eventHandler.handle(
Event.warn(
"--discard_analysis_cache was used in the previous build, "
+ "discarding analysis cache."));
logger.atInfo().log("Discarding analysis cache because the previous invocation told us to");
return true;
}

String diff =
describeConfigurationDifference(
originalConfigurationOptions, newOptions, maxDifferencesToShow);

if (diff == null && additionalConfigurationChangeEvent.isPresent()) {
diff = additionalConfigurationChangeEvent.get().getChangeDescription();
}

if (diff != null) {
if (!allowAnalysisCacheDiscards) {
String message = String.format("%s, analysis cache would have been discarded.", diff);
throw new InvalidConfigurationException(
message, FailureDetails.BuildConfiguration.Code.CONFIGURATION_DISCARDED_ANALYSIS_CACHE);
}
eventHandler.handle(
Event.warn(
diff
+ ", discarding analysis cache (this can be expensive, see"
+ " https://bazel.build/advanced/performance/iteration-speed)."));
logger.atInfo().log(
"Discarding analysis cache because the build configuration changed: %s", diff);
return true;
}

return false;
}

/** Sets the configuration. Not thread-safe. */
@VisibleForTesting
public void setConfiguration(
BuildConfigurationValue configuration,
BuildOptions originalOptions,
boolean discardAnalysisCache) {
if (discardAnalysisCache) {
// Note that clearing the analysis cache is currently required for correctness. It is also
// helpful to save memory.
//
// If we had more memory, fixing the correctness issue (see also b/144932999) would allow us
// to not invalidate the cache, leading to potentially better performance on incremental
// builds.
this.configuration = configuration;
this.originalConfigurationOptions = originalOptions;
skyframeExecutor.handleAnalysisInvalidatingChange();
} else {
String diff = describeConfigurationDifference(configuration, maxDifferencesToShow);
if (diff == null && additionalConfigurationChangeEvent.isPresent()) {
diff = additionalConfigurationChangeEvent.get().getChangeDescription();
}
} else if (this.configuration == null) {
this.configuration = configuration;
if (diff != null) {
if (!allowAnalysisCacheDiscards) {
String message = String.format("%s, analysis cache would have been discarded.", diff);
throw new InvalidConfigurationException(
message,
FailureDetails.BuildConfiguration.Code.CONFIGURATION_DISCARDED_ANALYSIS_CACHE);
}
eventHandler.handle(
Event.warn(
diff
+ ", discarding analysis cache (this can be expensive, see"
+ " https://bazel.build/advanced/performance/iteration-speed)."));
logger.atInfo().log(
"Discarding analysis cache because the build configuration changed: %s", diff);
// Note that clearing the analysis cache is currently required for correctness. It is also
// helpful to save memory.
//
// If we had more memory, fixing the correctness issue (see also b/144932999) would allow us
// to not invalidate the cache, leading to potentially better performance on incremental
// builds.
skyframeExecutor.handleAnalysisInvalidatingChange();
}
this.originalConfigurationOptions = originalOptions;
}

skyframeAnalysisWasDiscarded = false;
Expand Down Expand Up @@ -1391,6 +1414,7 @@ void clearLegacyData() {
*/
public void reset() {
configuration = null;
originalConfigurationOptions = null;
skyframeAnalysisWasDiscarded = false;
clearLegacyData();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1655,10 +1655,30 @@ public void throwsIfAnalysisCacheIsDiscardedWhenOptionSet() throws Exception {
normal_lib(name = "top")
""");
useConfiguration("--definitely_relevant=old");

// Set up the analysis cache
update("//test:top");
useConfiguration("--noallow_analysis_cache_discard", "--definitely_relevant=new");

// Check if things work if the build options are not changed
useConfiguration("--noallow_analysis_cache_discard", "--definitely_relevant=old");
update("//test:top");

// Check if an error is raised when the build options are changed. Do it twice because
// had already had a bug that the second invocation erroneously worked. See
// https://github.com/bazelbuild/bazel/issues/23491 .
useConfiguration("--noallow_analysis_cache_discard", "--definitely_relevant=new");
Throwable t = assertThrows(InvalidConfigurationException.class, () -> update("//test:top"));
assertThat(t.getMessage().contains("analysis cache would have been discarded")).isTrue();

t = assertThrows(InvalidConfigurationException.class, () -> update("//test:top"));
assertThat(t.getMessage()).contains("analysis cache would have been discarded");

// Check if going back to the original configuration works.
useConfiguration("--noallow_analysis_cache_discard", "--definitely_relevant=old");
update("//test:top");

// Now check if removing --noallow_analysis_cache_discard in fact allows discarding the cache.
useConfiguration("--definitely_relevant=new");
update("//test:top");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.ExtendedEventHandler;
import com.google.devtools.build.lib.events.StoredEventHandler;
import com.google.devtools.build.lib.packages.NoSuchPackageException;
Expand Down Expand Up @@ -261,21 +260,8 @@ public AnalysisResult update(
}

/** Sets the configuration. Not thread-safe. */
public void setConfigurationForTesting(
EventHandler eventHandler, BuildConfigurationValue configuration) {
try {
skyframeBuildView.setConfiguration(
eventHandler,
configuration,
/* maxDifferencesToShow= */ -1, /* allowAnalysisCacheDiscards */
true,
/* additionalConfigurationChangeEvent= */ Optional.empty());
} catch (InvalidConfigurationException e) {
throw new UnsupportedOperationException(
"InvalidConfigurationException was thrown and caught during a test, "
+ "this case is not yet handled",
e);
}
public void setConfigurationForTesting(BuildConfigurationValue configuration) {
skyframeBuildView.setConfiguration(configuration, configuration.getOptions(), true);
}

public ArtifactFactory getArtifactFactory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ protected final void createBuildView() {
skyframeExecutor.handleAnalysisInvalidatingChange();

view = new BuildViewForTesting(directories, ruleClassProvider, skyframeExecutor, null);
view.setConfigurationForTesting(event -> {}, targetConfig);
view.setConfigurationForTesting(targetConfig);

view.setArtifactRoots(new PackageRootsNoSymlinkCreation(Root.fromPath(rootDirectory)));
}
Expand Down

0 comments on commit dc61274

Please sign in to comment.