Fix bottleneck in GitRefUpdatedListener The supermanifest plugin listens on Git ref updates using a class marked as @Singleton. This means any and all updates to the Git storage will go through this class. The actual method that handles these updates was marked synchronized to prevent shared access to a non-thread safe config. This turns out to be a central bottleneck in Gerrit: All git updates have to go through this method. If the supermanifest plugin is configured to do work (i.e. update manifest entries for other repos), that work is actually quite slow (5s and sometimes above). This makes it so that updates on the Gerrit server get to a grinding halt and get all queued up on this synchronized block. In this commit, we rework the mutable config to be immutable, remove the synchronized block and swap it for an AtomicReference. With that, the machinery is still thread safe, but doesn't lock up anymore. While at it, we swap a call to ProjectIndex#evictAndReindex for ProjectIndex#evict. We just want to read a fresh project config from disk. Eviction is sufficient for that. Change-Id: I89ffce984d7f4c32dda72dff9e55a5c4c108ee7d
diff --git a/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java b/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java index 82baf05..788d673 100644 --- a/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java +++ b/java/com/googlesource/gerrit/plugins/supermanifest/ConfigEntry.java
@@ -17,7 +17,7 @@ import static com.google.common.base.Strings.nullToEmpty; import static com.google.gerrit.entities.RefNames.REFS_HEADS; -import com.google.common.collect.Sets; +import com.google.common.collect.ImmutableSet; import com.google.gerrit.entities.Project; import java.net.URI; import java.net.URISyntaxException; @@ -31,19 +31,19 @@ public class ConfigEntry { public static final String SECTION_NAME = "superproject"; - Project.NameKey srcRepoKey; - String srcRef; - URI baseUri; - ToolType toolType; - String xmlPath; - Project.NameKey destRepoKey; - String repoGroups; - Set<String> srcRefsExcluded; - boolean recordSubmoduleLabels; - boolean ignoreRemoteFailures; + final Project.NameKey srcRepoKey; + final String srcRef; + final URI baseUri; + final ToolType toolType; + final String xmlPath; + final Project.NameKey destRepoKey; + final String repoGroups; + final ImmutableSet<String> srcRefsExcluded; + final boolean recordSubmoduleLabels; + final boolean ignoreRemoteFailures; // destBranch can be "*" in which case srcRef is ignored. - String destBranch; + final String destBranch; public ConfigEntry(Config cfg, String name) throws ConfigInvalidException { String[] parts = name.split(":"); @@ -106,7 +106,7 @@ } srcRefsExcluded = - Sets.newHashSet( + ImmutableSet.copyOf( Arrays.asList(nullToEmpty(cfg.getString(SECTION_NAME, name, "exclude")).split(","))); xmlPath = cfg.getString(SECTION_NAME, name, "srcPath");
diff --git a/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java b/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java index 8f685f2..01d789e 100644 --- a/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java +++ b/java/com/googlesource/gerrit/plugins/supermanifest/SuperManifestRefUpdatedListener.java
@@ -17,6 +17,7 @@ import static com.google.gerrit.entities.RefNames.REFS_HEADS; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import com.google.common.flogger.FluentLogger; import com.google.errorprone.annotations.FormatMethod; import com.google.errorprone.annotations.FormatString; @@ -63,6 +64,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.RefNotFoundException; @@ -104,7 +106,7 @@ private final Timer1<ConfigEntry.ToolType> superprojectCommitTimer; // Mutable. - private Set<ConfigEntry> config; + private AtomicReference<ImmutableSet<ConfigEntry>> config = new AtomicReference<>(); @Inject SuperManifestRefUpdatedListener( @@ -252,12 +254,13 @@ /** for debugging. */ private String configurationToString() { - if (config == null) { + Set<ConfigEntry> cfg = config.get(); + if (cfg == null) { return "No config loaded (could not read All-Projects)"; } StringBuilder b = new StringBuilder(); - b.append("Supermanifest config (").append(config.size()).append(") {\n"); - for (ConfigEntry c : config) { + b.append("Supermanifest config (").append(cfg.size()).append(") {\n"); + for (ConfigEntry c : cfg) { b.append(" ").append(c).append("\n"); } b.append("}\n"); @@ -278,17 +281,17 @@ } } - config = filtered; + config.set(ImmutableSet.copyOf(filtered)); } @Override - public synchronized void onGitReferenceUpdated(Event event) { + public void onGitReferenceUpdated(Event event) { if (event.getProjectName().equals(allProjectsName.get())) { if (event.getRefName().equals("refs/meta/config")) { try { // TODO: Remove, this is just band-aid. // Evict project cache because this is called before that eviction is done in core - projectCache.evictAndReindex(allProjectsName); + projectCache.evict(allProjectsName); updateConfiguration(); } catch (NoSuchProjectException e) { throw new IllegalStateException(e); @@ -334,7 +337,7 @@ identifiedUser.get().getAccountId().get(), configurationToString()); - if (config == null) { + if (config.get() == null) { error( "Plugin could not read conf from All-Projects (processing %s:%s)", manifestProject, manifestBranch); @@ -364,11 +367,12 @@ } private List<ConfigEntry> findRelevantConfigs(String project, String refName) { + Set<ConfigEntry> cfg = config.get(); List<ConfigEntry> relevantConfigs = new ArrayList<>(); - if (config == null) { + if (cfg == null) { return relevantConfigs; } - for (ConfigEntry c : config) { + for (ConfigEntry c : cfg) { if (!c.srcRepoKey.get().equals(project)) { continue; }