com.microsoft.azure.documentdb.internal.EndpointDiscoveryRetryPolicy Maven / Gradle / Ivy
package com.microsoft.azure.documentdb.internal;
import java.net.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.microsoft.azure.documentdb.ConnectionPolicy;
import com.microsoft.azure.documentdb.DocumentClientException;
final class EndpointDiscoveryRetryPolicy implements RetryPolicy {
private final static int MAX_RETRY_COUNT = 120;
private final static int RETRY_INTERVAL_IN_MS = 1000;
private final static Logger LOGGER = LoggerFactory.getLogger(EndpointDiscoveryRetryPolicy.class);
private final EndpointManager globalEndpointManager;
private final ConnectionPolicy connectionPolicy;
private final DocumentServiceRequest request;
private URI locationEndpoint;
private int failoverRetryCount = 0;
/**
* A RetryPolicy implementation that handles endpoint change exceptions.
* @param connectionPolicy connection policy
* @param globalEndpointManager endpoint manager
*/
public EndpointDiscoveryRetryPolicy(ConnectionPolicy connectionPolicy,
EndpointManager globalEndpointManager,
DocumentServiceRequest request) {
this.connectionPolicy = connectionPolicy;
this.globalEndpointManager = globalEndpointManager;
// clear previous location-based routing directive
request.clearRouteToLocation();
// Resolve the endpoint for the request and pin the resolution to the resolved endpoint
// This enables marking the endpoint unavailability on endpoint failover/unreachability
this.locationEndpoint = this.globalEndpointManager.resolveServiceEndpoint(request);
request.routeToLocation(this.locationEndpoint);
this.request = request;
}
/**
* Gets the number of milliseconds to wait before retry the operation.
*
* @return the number of milliseconds to wait before retry the operation.
*/
public long getRetryAfterInMilliseconds() {
if (!this.request.isReadOnlyRequest()) {
return this.failoverRetryCount <= 1 ?
0 :
RETRY_INTERVAL_IN_MS; // if retried both endpoints, follow regular retry intervals
} else {
return RETRY_INTERVAL_IN_MS;
}
}
/**
* Should the caller retry the operation.
*
* This retry policy should only be invoked if HttpStatusCode is 403 (Forbidden)
* and SubStatusCode is 3 (WriteForbidden).
*
* @param exception the exception to check.
* @return true if should retry.
*/
public boolean shouldRetry(DocumentClientException exception) {
if (!this.connectionPolicy.getEnableEndpointDiscovery()) {
return false;
}
if (this.failoverRetryCount >= MAX_RETRY_COUNT) {
return false;
}
this.failoverRetryCount++;
if (this.locationEndpoint != null) {
if (request.isReadOnlyRequest()) {
// Mark current read endpoint as unavailable
globalEndpointManager.markEndpointUnavailableForRead(this.locationEndpoint);
} else {
globalEndpointManager.markEndpointUnavailableForWrite(this.locationEndpoint);
}
}
if (!this.request.isReadOnlyRequest()) {
LOGGER.debug(String.format("Failover happening. retryCount %d", this.failoverRetryCount));
}
boolean forceRefresh = exception.getSubStatusCode() != null &&
exception.getSubStatusCode() == HttpConstants.SubStatusCodes.FORBIDDEN_WRITEFORBIDDEN;
this.globalEndpointManager.refreshEndpointList(null, forceRefresh);
// clear previous location-based routing directive
request.clearRouteToLocation();
// set location-based routing directive based on retry count
// simulating single master writes by ensuring usePreferredLocations
// is set to false
request.routeToLocation(this.failoverRetryCount, false);
// Resolve the endpoint for the request and pin the resolution to the resolved endpoint
// This enables marking the endpoint unavailability on endpoint failover/unreachability
this.locationEndpoint = this.globalEndpointManager.resolveServiceEndpoint(request);
request.routeToLocation(this.locationEndpoint);
return true;
}
}