From 12fb1cf1c38c4cab76399beac8f5d8abe7507d72 Mon Sep 17 00:00:00 2001 From: Pichatorn-A4K Date: Fri, 11 Sep 2026 02:18:28 +0700 Subject: [PATCH] Bound the 429 retry and back off exponentially The rate-limit branch slept a fixed 5 seconds and then re-entered Request() recursively with no attempt ceiling, with a '// TODO - Figure out how long to wait' left in place. DigiCert documents two limits - 1000 requests per 3 minutes and 100 per 5 seconds, rolling, per API key - and recommends exponential backoff with a default maximum of 3 retries. Its 429 responses carry no Retry-After header, so there is nothing to read and the client has to choose the interval. A fixed 5-second wait only clears the burst window, never the 3-minute one, so tripping the larger limit turned into an indefinite 5-second poll with a stack frame per attempt instead of an error the caller could surface. This is easy to trip on a large account: a full synchronization issues roughly four GETs per order plus one per certificate. Now 5s, 10s, 20s, then the 429 body is returned to the caller as an error. --- .../Client/CertCentralClient.cs | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/digicert-certcentral-caplugin/Client/CertCentralClient.cs b/digicert-certcentral-caplugin/Client/CertCentralClient.cs index 753f54f..9060b22 100644 --- a/digicert-certcentral-caplugin/Client/CertCentralClient.cs +++ b/digicert-certcentral-caplugin/Client/CertCentralClient.cs @@ -73,7 +73,14 @@ private CertCentralResponse Request(CertCentralBaseRequest request) private static int RequestIDCounter = 1; + private const int MaxRateLimitRetries = 3; + private CertCentralResponse Request(CertCentralBaseRequest request, string parameters) + { + return Request(request, parameters, 1); + } + + private CertCentralResponse Request(CertCentralBaseRequest request, string parameters, int attempt) { //set in config files //ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; @@ -129,10 +136,28 @@ private CertCentralResponse Request(CertCentralBaseRequest request, string param { if (errorResponse.StatusCode == (HttpStatusCode)429/*Too Many Requests*/) { - Logger.LogInformation($"Request ID: {reqID} was rate-limited. Trying again in 5 seconds"); - // TODO - Figure out how long to wait, then wait that long - System.Threading.Thread.Sleep(5000); - return Request(request, parameters); + // DigiCert's documented limits are 1000 requests / 3 minutes AND 100 / 5 + // seconds, rolling, per API key, and its guidance is exponential backoff + // with a default maximum of 3 retries. A fixed 5-second wait only ever + // clears the burst window, never the 3-minute one, and the retry was + // unbounded, so a genuine rate-limit became an endless 5-second poll with a + // growing stack rather than an error the caller could report. + if (attempt >= MaxRateLimitRetries) + { + Logger.LogWarning($"Request ID: {reqID} was rate-limited by DigiCert and has exhausted {MaxRateLimitRetries} attempts. Giving up."); + using (var limitReader = new StreamReader(errorResponse.GetResponseStream())) + { + oCertCertResponse.Success = false; + oCertCertResponse.Response = limitReader.ReadToEnd(); + } + } + else + { + int waitSeconds = 5 * (int)Math.Pow(2, attempt - 1); + Logger.LogInformation($"Request ID: {reqID} was rate-limited. Retry {attempt} of {MaxRateLimitRetries - 1} in {waitSeconds} seconds"); + System.Threading.Thread.Sleep(waitSeconds * 1000); + return Request(request, parameters, attempt + 1); + } } else {