Merge "Allow to set a max size for the in-memory code owner config cache"
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/TransientCodeOwnerConfigCache.java b/java/com/google/gerrit/plugins/codeowners/backend/TransientCodeOwnerConfigCache.java
index 61f014b..81eea89 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/TransientCodeOwnerConfigCache.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/TransientCodeOwnerConfigCache.java
@@ -17,6 +17,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.plugins.codeowners.backend.config.CodeOwnersPluginConfiguration;
 import com.google.gerrit.plugins.codeowners.metrics.CodeOwnerMetrics;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.inject.Inject;
@@ -38,14 +39,19 @@
 public class TransientCodeOwnerConfigCache implements CodeOwnerConfigLoader {
   private final GitRepositoryManager repoManager;
   private final CodeOwners codeOwners;
+  private final Optional<Integer> maxCacheSize;
   private final Counters counters;
   private final HashMap<CacheKey, Optional<CodeOwnerConfig>> cache = new HashMap<>();
 
   @Inject
   TransientCodeOwnerConfigCache(
-      GitRepositoryManager repoManager, CodeOwners codeOwners, CodeOwnerMetrics codeOwnerMetrics) {
+      CodeOwnersPluginConfiguration codeOwnersPluginConfiguration,
+      GitRepositoryManager repoManager,
+      CodeOwners codeOwners,
+      CodeOwnerMetrics codeOwnerMetrics) {
     this.repoManager = repoManager;
     this.codeOwners = codeOwners;
+    this.maxCacheSize = codeOwnersPluginConfiguration.getMaxCodeOwnerConfigCacheSize();
     this.counters = new Counters(codeOwnerMetrics);
   }
 
@@ -89,7 +95,9 @@
         codeOwnerConfig = Optional.empty();
       }
     }
-    cache.put(cacheKey, codeOwnerConfig);
+    if (!maxCacheSize.isPresent() || cache.size() < maxCacheSize.get()) {
+      cache.put(cacheKey, codeOwnerConfig);
+    }
     return codeOwnerConfig;
   }
 
diff --git a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java
index 0855baf..3af457d 100644
--- a/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java
+++ b/java/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfiguration.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import java.util.Optional;
 
 /**
  * The configuration of the code-owners plugin.
@@ -48,6 +49,8 @@
   @VisibleForTesting
   static final String KEY_ENABLE_EXPERIMENTAL_REST_ENDPOINTS = "enableExperimentalRestEndpoints";
 
+  private static final String KEY_MAX_CODE_OWNER_CONFIG_CACHE_SIZE = "maxCodeOwnerConfigCacheSize";
+
   private final CodeOwnersPluginConfigSnapshot.Factory codeOwnersPluginConfigSnapshotFactory;
   private final String pluginName;
   private final PluginConfigFactory pluginConfigFactory;
@@ -117,4 +120,31 @@
       return false;
     }
   }
+
+  /**
+   * Gets the maximum size for the {@link
+   * com.google.gerrit.plugins.codeowners.backend.TransientCodeOwnerConfigCache}.
+   *
+   * @return the maximum cache size, {@link Optional#empty()} if the cache size is not limited
+   */
+  public Optional<Integer> getMaxCodeOwnerConfigCacheSize() {
+    try {
+      int maxCodeOwnerConfigCacheSize =
+          pluginConfigFactory
+              .getFromGerritConfig(pluginName)
+              .getInt(KEY_MAX_CODE_OWNER_CONFIG_CACHE_SIZE, /* defaultValue= */ 0);
+      return maxCodeOwnerConfigCacheSize > 0
+          ? Optional.of(maxCodeOwnerConfigCacheSize)
+          : Optional.empty();
+    } catch (IllegalArgumentException e) {
+      logger.atWarning().withCause(e).log(
+          "Value '%s' in gerrit.config (parameter plugin.%s.%s) is invalid.",
+          pluginConfigFactory
+              .getFromGerritConfig(pluginName)
+              .getString(KEY_MAX_CODE_OWNER_CONFIG_CACHE_SIZE),
+          pluginName,
+          KEY_MAX_CODE_OWNER_CONFIG_CACHE_SIZE);
+      return Optional.empty();
+    }
+  }
 }
diff --git a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java
index 71c6849..ff53824 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/backend/config/CodeOwnersPluginConfigurationTest.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static com.google.gerrit.truth.OptionalSubject.assertThat;
 
 import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.entities.Project;
@@ -88,4 +89,29 @@
   public void experimentalRestEndpointsNotEnabled_invalidConfig() throws Exception {
     assertThat(codeOwnersPluginConfiguration.areExperimentalRestEndpointsEnabled()).isFalse();
   }
+
+  @Test
+  public void codeOwnerConfigCacheSizeIsUnlimitedByDefault() throws Exception {
+    assertThat(codeOwnersPluginConfiguration.getMaxCodeOwnerConfigCacheSize()).isEmpty();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.maxCodeOwnerConfigCacheSize", value = "0")
+  public void codeOwnerConfigCacheSizeIsUnlimited() throws Exception {
+    assertThat(codeOwnersPluginConfiguration.getMaxCodeOwnerConfigCacheSize()).isEmpty();
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.maxCodeOwnerConfigCacheSize", value = "10")
+  public void codeOwnerConfigCacheSizeIsLimited() throws Exception {
+    assertThat(codeOwnersPluginConfiguration.getMaxCodeOwnerConfigCacheSize())
+        .value()
+        .isEqualTo(10);
+  }
+
+  @Test
+  @GerritConfig(name = "plugin.code-owners.maxCodeOwnerConfigCacheSize", value = "invalid")
+  public void maxCodeOwnerConfigCacheSize_invalidConfig() throws Exception {
+    assertThat(codeOwnersPluginConfiguration.getMaxCodeOwnerConfigCacheSize()).isEmpty();
+  }
 }
diff --git a/resources/Documentation/config.md b/resources/Documentation/config.md
index a94cf63..6d45d59 100644
--- a/resources/Documentation/config.md
+++ b/resources/Documentation/config.md
@@ -398,6 +398,14 @@
         in `@PLUGIN@.config`.\
         By default `100`.
 
+<a id="pluginCodeOwnersMaxCodeOwnerConfigCacheSize">plugin.@PLUGIN@.maxCodeOwnerConfigCacheSize</a>
+:       When computing code owner file statuses for a change (e.g. to compute
+        the results for the code owners submit rule) parsed code owner config
+        files are cached in memory for the time of the request.\
+        This configuration parameter allows to set a limit for the number of
+        code owner config files that are cached per request.\
+        By default `0` (unlimited).
+
 # <a id="projectConfiguration">Project configuration in @PLUGIN@.config</a>
 
 <a id="codeOwnersDisabled">codeOwners.disabled</a>