in Source/HTTP/httpcall.cpp [186:294]
bool http_call_should_retry(
_In_ HCCallHandle call,
_In_ const chrono_clock_t::time_point& responseReceivedTime)
{
if (!call->retryAllowed)
{
return false;
}
if (call->networkErrorCode == E_HC_NO_NETWORK)
{
return false;
}
auto httpStatus = call->statusCode;
if (httpStatus == 408 || // Request Timeout
httpStatus == 429 || // Too Many Requests
httpStatus == 500 || // Internal Error
httpStatus == 502 || // Bad Gateway
httpStatus == 503 || // Service Unavailable
httpStatus == 504 || // Gateway Timeout
call->networkErrorCode != S_OK)
{
std::chrono::milliseconds retryAfter = GetRetryAfterHeaderTime(call);
// Compute how much time left before hitting the TimeoutWindow setting
std::chrono::milliseconds timeElapsedSinceFirstCall = std::chrono::duration_cast<std::chrono::milliseconds>(responseReceivedTime - call->firstRequestStartTime);
uint32_t timeoutWindowInSeconds = 0;
HCHttpCallRequestGetTimeoutWindow(call, &timeoutWindowInSeconds);
std::chrono::seconds timeoutWindow = std::chrono::seconds(timeoutWindowInSeconds);
std::chrono::milliseconds remainingTimeBeforeTimeout = timeoutWindow - timeElapsedSinceFirstCall;
if (call->traceCall) { HC_TRACE_INFORMATION(HTTPCLIENT, "HCHttpCallPerformExecute [ID %llu] remainingTimeBeforeTimeout %lld ms", TO_ULL(call->id), remainingTimeBeforeTimeout.count()); }
// Based on the retry iteration, delay 2,4,8,16,etc seconds by default between retries
// Jitter the response between the current and next delay based on system clock
// Max wait time is 1 minute
uint32_t retryDelayInSeconds = 0;
HCHttpCallRequestGetRetryDelay(call, &retryDelayInSeconds);
double secondsToWaitMin = std::pow(retryDelayInSeconds, call->retryIterationNumber);
double secondsToWaitMax = std::pow(retryDelayInSeconds, call->retryIterationNumber + 1);
double secondsToWaitDelta = secondsToWaitMax - secondsToWaitMin;
double lerpScaler = (responseReceivedTime.time_since_epoch().count() % 10000) / 10000.0; // from 0 to 1 based on clock
#if HC_UNITTEST_API
lerpScaler = 0; // make unit tests deterministic
#endif
double secondsToWaitUncapped = secondsToWaitMin + secondsToWaitDelta * lerpScaler; // lerp between min & max wait
double secondsToWait = std::min(secondsToWaitUncapped, MAX_DELAY_TIME_IN_SEC); // cap max wait to 1 min
std::chrono::milliseconds waitTime = std::chrono::milliseconds(static_cast<int64_t>(secondsToWait * 1000.0));
if (retryAfter.count() > 0)
{
// Jitter to spread the load of Retry-After out between the devices trying to retry
std::chrono::milliseconds retryAfterMin = retryAfter;
std::chrono::milliseconds retryAfterMax = std::chrono::milliseconds(static_cast<int64_t>(retryAfter.count() * 1.2));
auto retryAfterDelta = retryAfterMax.count() - retryAfterMin.count();
std::chrono::milliseconds retryAfterJittered = std::chrono::milliseconds(static_cast<int64_t>(retryAfterMin.count() + retryAfterDelta * lerpScaler)); // lerp between min & max wait
// Use either the waitTime or the jittered Retry-After header, whichever is bigger
call->delayBeforeRetry = std::chrono::milliseconds(std::max(waitTime.count(), retryAfterJittered.count()));
}
else
{
call->delayBeforeRetry = waitTime;
}
if (call->traceCall) { HC_TRACE_INFORMATION(HTTPCLIENT, "HCHttpCallPerformExecute [ID %llu] delayBeforeRetry %lld ms", TO_ULL(call->id), call->delayBeforeRetry.count()); }
if (httpStatus == 500) // Internal Error
{
// For 500 - Internal Error, wait at least 10 seconds before retrying.
if (call->delayBeforeRetry.count() < MIN_DELAY_FOR_HTTP_INTERNAL_ERROR_IN_MS)
{
call->delayBeforeRetry = std::chrono::milliseconds(MIN_DELAY_FOR_HTTP_INTERNAL_ERROR_IN_MS);
if (call->traceCall) { HC_TRACE_INFORMATION(HTTPCLIENT, "HCHttpCallPerformExecute [ID %llu] 500: delayBeforeRetry %lld ms", TO_ULL(call->id), call->delayBeforeRetry.count()); }
}
}
bool shouldRetry{ true };
if (remainingTimeBeforeTimeout.count() <= MIN_HTTP_TIMEOUT_IN_MS)
{
// Need at least 5 seconds to bother making a call
shouldRetry = false;
}
else if (remainingTimeBeforeTimeout < call->delayBeforeRetry + std::chrono::milliseconds(MIN_HTTP_TIMEOUT_IN_MS))
{
// Don't bother retrying when out of time
shouldRetry = false;
}
// Remember result if there was an error and there was a Retry-After header
if (call->retryAfterCacheId != 0 &&
retryAfter.count() > 0 &&
httpStatus > 400)
{
auto retryAfterTime = retryAfter + responseReceivedTime;
http_retry_after_api_state state{ retryAfterTime, httpStatus, shouldRetry };
auto httpSingleton = get_http_singleton();
if (httpSingleton)
{
httpSingleton->set_retry_state(call->retryAfterCacheId, state);
}
}
return shouldRetry;
}
return false;
}