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();