Skip to content

Commit

Permalink
Cache blaze info during sync
Browse files Browse the repository at this point in the history
Currently, BlazeInfo will be computed 5 times during incremental sync, this commit introduces logic to cache it and re-use the computed value during incremental syncs.

`BlazeInfoProvider` was introduced in order to avoid changing `BlazeInfoRunner` and keep its responsibility to solely execute the info command.

Logic is guarded by `blaze.info.provider.enabled` which is disabled by default.
  • Loading branch information
idanakav committed Aug 8, 2023
1 parent b99aa06 commit 3598ec6
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 10 deletions.
1 change: 1 addition & 0 deletions base/src/META-INF/blaze-base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@
<SyncListener implementation="com.google.idea.blaze.base.sync.libraries.ExternalLibraryManager$StartSyncListener"/>
<SyncListener implementation="com.google.idea.blaze.base.sync.autosync.ProjectTargetManagerImpl$TargetSyncListener"/>
<SyncListener implementation="com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProviderImpl$Listener"/>
<SyncListener implementation="com.google.idea.blaze.base.command.info.BlazeInfoProvider$Invalidator"/>
<SyncPlugin implementation="com.google.idea.blaze.base.lang.buildfile.sync.BuildLangSyncPlugin"/>
<SyncPlugin implementation="com.google.idea.blaze.base.sync.libraries.ExternalLibraryManager$SyncPlugin"/>
<BuildFlagsProvider implementation="com.google.idea.blaze.base.command.BuildFlagsProviderImpl"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.idea.blaze.base.command.buildresult.BuildResultHelper;
import com.google.idea.blaze.base.command.buildresult.BuildResultHelperBep;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.command.info.BlazeInfoProvider;
import com.google.idea.blaze.base.command.info.BlazeInfoRunner;
import com.google.idea.blaze.base.projectview.ProjectViewManager;
import com.google.idea.blaze.base.projectview.ProjectViewSet;
Expand Down Expand Up @@ -144,7 +145,15 @@ private ListenableFuture<BlazeInfo> runBlazeInfo() {
BlazeCommandName.INFO,
blazeContext,
BlazeInvocationContext.SYNC_CONTEXT);
if (BlazeInfoProvider.isEnabled()) {
return BlazeInfoProvider.getInstance(project)
.getBlazeInfo(blazeContext, syncFlags);
}
return BlazeInfoRunner.getInstance()
.runBlazeInfo(project, this, blazeContext, buildSystem.getName(), syncFlags);
.runBlazeInfo(project,
this,
blazeContext,
buildSystem.getName(),
syncFlags);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2023 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.idea.blaze.base.command.info;

import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.idea.blaze.base.async.executor.BlazeExecutor;
import com.google.idea.blaze.base.bazel.BuildSystem;
import com.google.idea.blaze.base.scope.BlazeContext;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.base.settings.BlazeImportSettings;
import com.google.idea.blaze.base.settings.BlazeImportSettingsManager;
import com.google.idea.blaze.base.sync.SyncListener;
import com.google.idea.blaze.base.sync.SyncMode;
import com.google.idea.blaze.base.sync.SyncResult;
import com.google.idea.common.experiments.BoolExperiment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.concurrent.ExecutionException;

/**
* <p> Providing a {@link com.google.idea.blaze.base.command.info.BlazeInfo}, the result will be cached and invalidated on any sync failure. </p>
*
* @see BlazeInfoRunner
*/
public class BlazeInfoProvider {
private static final Logger logger = Logger.getInstance(BlazeInfoProvider.class);
private static final BoolExperiment enabled =
new BoolExperiment("blaze.info.provider.enabled", false);

private final Project project;
private volatile BlazeInfo blazeInfo;

public BlazeInfoProvider(Project project) {
this.project = project;
}

public static BlazeInfoProvider getInstance(Project project) {
return project.getService(BlazeInfoProvider.class);
}

public static Boolean isEnabled() {
return enabled.getValue();
}

public ListenableFuture<String> getBlazeInfo(
BlazeContext context,
List<String> blazeFlags,
String key) {
ListenableFuture<BlazeInfo> future = getBlazeInfo(context,
blazeFlags);
return Futures.transform(future,
info -> info.get(key),
BlazeExecutor.getInstance().getExecutor()
);
}

public ListenableFuture<BlazeInfo> getBlazeInfo(
BlazeContext context,
List<String> blazeFlags) {
return BlazeExecutor.getInstance().submit(() -> {
BlazeInfo info = getCachedBlazeInfo(context,
blazeFlags);
if (info == null) {
throw new BlazeInfoException("Unable to get blaze info");
}
return info;
});
}

private @Nullable BlazeInfo getCachedBlazeInfo(
BlazeContext context,
List<String> blazeFlags) {
if (blazeInfo != null) {
logger.debug("Returning cached BlazeInfo");
return blazeInfo;
}
try {
BlazeImportSettings importSettings = BlazeImportSettingsManager.getInstance(project).getImportSettings();
if (importSettings == null) {
return null;
}
BuildSystem.BuildInvoker buildInvoker = Blaze.getBuildSystemProvider(project)
.getBuildSystem()
.getDefaultInvoker(project, context);
logger.debug("Running bazel info");
blazeInfo = BlazeInfoRunner.getInstance().runBlazeInfo(
project,
buildInvoker,
BlazeContext.create(),
importSettings.getBuildSystem(),
blazeFlags
).get();
return blazeInfo;
} catch (InterruptedException | ExecutionException e) {
logger.warn("Unable to run blaze info", e);
return null;
}
}

public void invalidate() {
logger.debug("invalidating");
blazeInfo = null;
}

public static final class Invalidator implements SyncListener {
@Override
public void afterSync(
Project project,
BlazeContext context,
SyncMode syncMode,
SyncResult syncResult,
ImmutableSet<Integer> buildIds) {
if(!syncResult.successful()) {
BlazeInfoProvider.getInstance(project).invalidate();
}
}
}
}
34 changes: 25 additions & 9 deletions base/src/com/google/idea/blaze/base/sync/ProjectStateSyncTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.idea.blaze.base.command.BlazeFlags;
import com.google.idea.blaze.base.command.BlazeInvocationContext;
import com.google.idea.blaze.base.command.info.BlazeInfo;
import com.google.idea.blaze.base.command.info.BlazeInfoProvider;
import com.google.idea.blaze.base.command.info.BlazeInfoRunner;
import com.google.idea.blaze.base.io.FileOperationProvider;
import com.google.idea.blaze.base.model.BlazeVersionData;
Expand Down Expand Up @@ -115,15 +116,7 @@ private SyncProjectState getProjectState(BlazeContext context, BlazeSyncParams p
BlazeInvocationContext.SYNC_CONTEXT);

ListenableFuture<BlazeInfo> blazeInfoFuture =
BlazeInfoRunner.getInstance()
.runBlazeInfo(
project,
Blaze.getBuildSystemProvider(project)
.getBuildSystem()
.getDefaultInvoker(project, context),
context,
importSettings.getBuildSystem(),
syncFlags);
createBazelInfoFuture(context, syncFlags, params.syncMode());

ListenableFuture<WorkingSet> workingSetFuture;
if(params.addWorkingSet() || params.syncMode() == SyncMode.FULL) {
Expand Down Expand Up @@ -189,6 +182,29 @@ private SyncProjectState getProjectState(BlazeContext context, BlazeSyncParams p
.build();
}

private ListenableFuture<BlazeInfo> createBazelInfoFuture(
BlazeContext context,
List<String> syncFlags,
SyncMode syncMode) {
boolean useBazelInfoRunner = !BlazeInfoProvider.isEnabled() || syncMode == SyncMode.FULL;
if (useBazelInfoRunner) {
return BlazeInfoRunner.getInstance()
.runBlazeInfo(
project,
Blaze.getBuildSystemProvider(project)
.getBuildSystem()
.getDefaultInvoker(project,
context),
context,
importSettings.getBuildSystem(),
syncFlags);
}
return BlazeInfoProvider.getInstance(project)
.getBlazeInfo(
context,
syncFlags);
}

private static class WorkspacePathResolverAndProjectView {
final WorkspacePathResolver workspacePathResolver;
final ProjectViewSet projectViewSet;
Expand Down

1 comment on commit 3598ec6

@idanakav
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A couple of open questions:

  1. When should we invalidate the BlazeInfo cache? maybe on .bazelversion WORKSPACE file changes?
  2. Should we just use SyncCache and compute BlazeInfo once per sync?

Please sign in to comment.