All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy