com.microsoft.azure.documentdb.internal.directconnectivity.GoneAndRetryWithRetryPolicy Maven / Gradle / Ivy
package com.microsoft.azure.documentdb.internal.directconnectivity;
import com.microsoft.azure.documentdb.DocumentClientException;
import com.microsoft.azure.documentdb.internal.BridgeInternal;
import com.microsoft.azure.documentdb.internal.DocumentServiceRequest;
import com.microsoft.azure.documentdb.internal.HttpConstants;
import com.microsoft.azure.documentdb.internal.RetryPolicy;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used internally to represents a retry policy for Gone exception in the Azure Cosmos DB database service.
*/
public class GoneAndRetryWithRetryPolicy implements RetryPolicy {
private final static int SECONDS_TO_MILLIS = 1000;
private final static int INITIALI_BACKOFF_SEONDS = 1;
public final static int WAIT_TIME_IN_SECONDS = 30;
public final static int INITIALI_BACKOFF_MILLISEONDS = INITIALI_BACKOFF_SEONDS * SECONDS_TO_MILLIS;
public final static int BACKOFF_MULTIPLIER = 2;
private final Logger LOGGER = LoggerFactory.getLogger(GoneAndRetryWithRetryPolicy.class);
private int attemptCount = 1;
private DocumentClientException lastRetryWithException;
private long currentBackOffMilliSeconds = INITIALI_BACKOFF_MILLISEONDS;
private DocumentServiceRequest request;
private long startTimeMilliSeconds;
private long retryAfterMilliSeconds;
private boolean retryPartitionKeyRangeIdentity = true;
private long retryWithInitialBackoffMilliseconds;
private int retryWithBackoffMultiplier;
public GoneAndRetryWithRetryPolicy(DocumentServiceRequest request,
long retryWithInitialBackoffMilliseconds,
int retryWithBackoffMultiplier
) {
this.request = request;
this.startTimeMilliSeconds = System.currentTimeMillis();
this.retryWithBackoffMultiplier = retryWithBackoffMultiplier > 0 ? retryWithBackoffMultiplier : BACKOFF_MULTIPLIER;
this.retryWithInitialBackoffMilliseconds = retryWithInitialBackoffMilliseconds > 0 ? retryWithInitialBackoffMilliseconds : INITIALI_BACKOFF_MILLISEONDS;
}
@Override
public boolean shouldRetry(DocumentClientException exception) throws DocumentClientException {
if (exception.getStatusCode() != HttpStatus.SC_GONE && exception.getStatusCode() != HttpConstants.StatusCodes.RETRY_WITH) {
return false;
}
Integer substatusCode = exception.getSubStatusCode();
if (substatusCode != null &&
substatusCode.intValue() == HttpConstants.SubStatusCodes.PARTITION_KEY_RANGE_GONE &&
BridgeInternal.isUserProvidedPartitionKeyRangeIdentity(this.request)) {
if (retryPartitionKeyRangeIdentity) {
request.setForcePartitionKeyRangeRefresh(true);
retryPartitionKeyRangeIdentity = false;
} else {
LOGGER.warn("Received invalid partition id from user, retry won't happen, failing the request", exception);
return false;
}
}
if (exception.getStatusCode() == HttpConstants.StatusCodes.RETRY_WITH) {
this.lastRetryWithException = exception;
}
this.retryAfterMilliSeconds = 0;
// Don't penalise first retry with delay.
if (this.attemptCount++ > 1) {
long remainingSeconds = WAIT_TIME_IN_SECONDS - (int) ((System.currentTimeMillis() - this.startTimeMilliSeconds) * 1.0 / 1000.0);
if (remainingSeconds <= 0) {
if (exception.getStatusCode() == HttpStatus.SC_GONE) {
// Differentiate between Gone and InvalidPartition exceptions
String exceptionName = substatusCode != null
&& substatusCode == HttpConstants.SubStatusCodes.NAME_CACHE_IS_STALE
? "invalid partition exception"
: "gone exception";
if (this.lastRetryWithException != null) {
LOGGER.warn("Received {} after backoff/retry including at least one " +
"RetryWithException. Will fail the request with RetryWithException", exceptionName, this.lastRetryWithException);
throw this.lastRetryWithException;
} else {
LOGGER.warn("Received {} after backoff/retry. Will fail the request", exceptionName, exception);
throw new DocumentClientException(HttpStatus.SC_SERVICE_UNAVAILABLE, exception);
}
} else {
LOGGER.warn("Received retryWith exception after backoff/retry. Will fail the request", exception);
}
return false;
}
// Overrides are considered only if the first retry is for RETRY_WITH
// GONE followed by RETRY_WITH will still have default exponential behavior
int multiplierToUse = BACKOFF_MULTIPLIER;
if (exception.getStatusCode() == HttpConstants.StatusCodes.RETRY_WITH) {
multiplierToUse = this.retryWithBackoffMultiplier;
if (this.currentBackOffMilliSeconds == INITIALI_BACKOFF_MILLISEONDS) {
this.currentBackOffMilliSeconds = this.retryWithInitialBackoffMilliseconds;
}
}
this.retryAfterMilliSeconds = Math.min(this.currentBackOffMilliSeconds, remainingSeconds * SECONDS_TO_MILLIS);
this.currentBackOffMilliSeconds *= multiplierToUse;
}
if (exception.getStatusCode() == HttpStatus.SC_GONE) {
if (substatusCode != null && substatusCode.intValue() == HttpConstants.SubStatusCodes.NAME_CACHE_IS_STALE) {
request.setQuorumSelectedLSN(-1);
request.setQuorumSelectedStoreResponse(null);
request.setForceNameCacheRefresh(true);
LOGGER.debug("Received Gone exception with sub status name_cach_ is_stale "+substatusCode.intValue()+", will retry with name cache refresh");
} else if (substatusCode != null &&
(substatusCode.intValue() == HttpConstants.SubStatusCodes.COMPLETING_SPLIT ||
substatusCode.intValue() == HttpConstants.SubStatusCodes.COMPLETING_PARTITION_MIGRATION ||
substatusCode.intValue() == HttpConstants.SubStatusCodes.PARTITION_KEY_RANGE_GONE)) {
this.request.setForcePartitionKeyRangeRefresh(true);
LOGGER.debug("Received Gone exception with sub status "+substatusCode.intValue()+", will retry with partition key range cache refresh");
}
request.setForceAddressRefresh(true);
LOGGER.debug("Received Gone exception, will retry with address cache refresh");
} else {
LOGGER.debug("Received retryWith exception, will retry", exception);
}
return true;
}
@Override
public long getRetryAfterInMilliseconds() {
return this.retryAfterMilliSeconds;
}
}