I need to implement an exponential backoff on a request that might fail. However, it's implemented as an async request. Had this been done synchronously, I'd have a better idea on where to put the delay. Roughly, I'm thinking it'd work something like this:
// These would be configurable in application.yml
currentAttempt = 0;
maxAttempts = 3;
timeoutGrowth = 2;
currentDelayTime = 5ms;
repeatNeeded = false;
while(repeatNeeded && currentAttempt < maxAttempts) {
httpStatusCode = makeRequest(someService)
if(httpStatusCode == 503) {
repeatNeeded=true;
currentAttempt++;
currentDelayTime*=timeoutGrowthRate;
sleep(currentDelayTime)
}
}
However, with an async call, the caller to the function is given the time back to do something else until the Future is has something. Do I code the backoff within the getObservations() method below, or do I code this in the caller of that getObservations() method? Below is the call as it currently is:
public CompletableFuture getObservations(String text, Map<String, Object> bodyParams) throws URISyntaxException { URI uri = getUri(text); HttpRequest request = getRequest(uri, text, bodyParams); Map<String, String> contextMap = Optional.ofNullable(MDC.getCopyOfContextMap()).orElse(Collections.emptyMap());
Instant startTime = Instant.now();
return httpClient.sendAsync(request, BodyHandlers.ofString())
.exceptionally(ex -> {
throw new ExternalToolException(externalServiceConfig.getName(), ex);
})
.thenApply(response -> {
long toolRequestDurationMillis = ChronoUnit.MILLIS.between(startTime, Instant.now());
if (HttpStatus.valueOf(response.statusCode()).is2xxSuccessful()) {
ToolResponse toolResponse = processResponse(response, toolRequestDurationMillis);
logToolResponse(toolResponse);
return toolResponse;
}
log.error("{} returned non-200 response code: {}", externalServiceConfig.getName(), response.statusCode());
throw new ExternalToolException(externalServiceConfig.getName(), response.statusCode());
});
}