Add rate limiting for HTTP requests

The external REST APIs made from this lib module are rate limited to
8 per second. This can be changed with a config if needed.

Change-Id: I027e6e314f2b6591f940f319bc7c705376478db8
diff --git a/README.md b/README.md
index 697e66c..df14343 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,9 @@
 fetch account details using /accounts/{account-id}/detail and
 /accounts/{account-id}/external.ids REST APIs.
 
+The remote REST APIs fired from this lib module are rate limited to
+8 per second. This can be changed with a config if needed.
+
 
 ## Test scenarios:
 #### Add new email
@@ -44,6 +47,10 @@
 The entries in the accounts cache must be evicted after maxAge
 duration reaches.
 
+#### API throttling
+The remote REST APIs must be throttled based on the requestsPerSecond
+gerrit setting.
+
 
 # How to build
 
@@ -114,6 +121,17 @@
     httpPassword = ***
 ```
 
+### remote-gerrit-account-cache.requestsPerSecond
+
+The maximum rate at which the remote REST APIs are fired.
+By default, set to 8 per second.
+
+Example:
+```
+[remote-gerrit-account-cache]
+    requestsPerSecond = 10
+```
+
 ## Section cache
 
 ### cache.accounts.maxAge
diff --git a/src/main/java/com/googlesource/gerrit/plugins/remotegerritaccountcache/APIRateLimiter.java b/src/main/java/com/googlesource/gerrit/plugins/remotegerritaccountcache/APIRateLimiter.java
new file mode 100644
index 0000000..a388d98
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/remotegerritaccountcache/APIRateLimiter.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.remotegerritaccountcache;
+
+import static com.googlesource.gerrit.plugins.remotegerritaccountcache.AccountCacheImpl.Config.SECTION;
+
+import com.google.common.util.concurrent.RateLimiter;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.Optional;
+import org.eclipse.jgit.lib.Config;
+
+@Singleton
+public class APIRateLimiter {
+  private final RateLimiter limiter;
+
+  @Inject
+  APIRateLimiter(@GerritServerConfig Config config) {
+    limiter =
+        RateLimiter.create(
+            Double.parseDouble(
+                Optional.ofNullable(config.getString(SECTION, null, "requestsPerSecond"))
+                    .orElse("8.0")));
+  }
+
+  public RateLimiter getLimiter() {
+    return limiter;
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/remotegerritaccountcache/AccountCacheImpl.java b/src/main/java/com/googlesource/gerrit/plugins/remotegerritaccountcache/AccountCacheImpl.java
index a70e33a..03f0ee8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/remotegerritaccountcache/AccountCacheImpl.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/remotegerritaccountcache/AccountCacheImpl.java
@@ -245,6 +245,7 @@
     private final GitRepositoryManager repoManager;
     private final AllUsersName allUsersName;
     private final HttpClient client;
+    private final APIRateLimiter rateLimiter;
 
     @Inject
     Loader(
@@ -256,7 +257,8 @@
         Config config,
         ExternalIdKeyFactory externalIdKeyFactory,
         GitRepositoryManager repoManager,
-        AllUsersName allUsersName) {
+        AllUsersName allUsersName,
+        APIRateLimiter rateLimiter) {
       this.accountsUpdateFactory = accountsUpdateFactory;
       this.accountIndexerProvider = accountIndexerProvider;
       this.executor = executor;
@@ -265,6 +267,7 @@
       this.externalIdKeyFactory = externalIdKeyFactory;
       this.repoManager = repoManager;
       this.allUsersName = allUsersName;
+      this.rateLimiter = rateLimiter;
       this.client = getHttpClient();
     }
 
@@ -336,6 +339,7 @@
     }
 
     protected AccountDetailInfo getAccountFromRemoteSite(Account.Id accountId) {
+      rateLimiter.getLimiter().acquire();
       HttpRequest request =
           HttpRequest.newBuilder().uri(config.getAccountDetailUri(accountId)).GET().build();
       try {
@@ -354,6 +358,7 @@
 
     public List<ExternalId> getExternalIdsFromRemoteSite(Account.Id accountId)
         throws AccountNotFoundInRemoteGerritException {
+      rateLimiter.getLimiter().acquire();
       HttpRequest request =
           HttpRequest.newBuilder().uri(config.getExternalIdsUri(accountId)).GET().build();