@azure/cosmos
Version:
Microsoft Azure Cosmos DB Service Node.js SDK for NOSQL API
123 lines • 5.81 kB
JavaScript
import { StatusCodes } from "../common/statusCodes.js";
import { HTTPMethod, isReadRequest } from "../common/index.js";
import { Constants } from "../common/constants.js";
import { TimeoutErrorCode } from "../request/TimeoutError.js";
/**
* This class TimeoutFailoverRetryPolicy handles retries for read operations
* (including data plane,metadata, and query plan) in case of request timeouts
* (TimeoutError) or service unavailability (503 status code) by performing failover
* and retrying on other regions.
* @hidden
*/
export class TimeoutFailoverRetryPolicy {
globalEndpointManager;
headers;
methodType;
resourceType;
operationType;
enableEndPointDiscovery;
globalPartitionEndpointManager;
maxRetryAttemptCount = 120;
maxServiceUnavailableRetryCount = 1;
retryAfterInMs = 0;
failoverRetryCount = 0;
request;
locationEndpoint;
constructor(globalEndpointManager, headers, methodType, resourceType, operationType, enableEndPointDiscovery, globalPartitionEndpointManager) {
this.globalEndpointManager = globalEndpointManager;
this.headers = headers;
this.methodType = methodType;
this.resourceType = resourceType;
this.operationType = operationType;
this.enableEndPointDiscovery = enableEndPointDiscovery;
this.globalPartitionEndpointManager = globalPartitionEndpointManager;
}
/**
* Checks if a timeout request is valid for the timeout failover retry policy.
* A valid request should be a data plane, metadata, or query plan request.
* @returns
*/
isValidRequestForTimeoutError() {
const isQuery = Constants.HttpHeaders.IsQuery in this.headers;
const isQueryPlan = Constants.HttpHeaders.IsQueryPlan in this.headers;
if (this.methodType === HTTPMethod.get || isQuery || isQueryPlan) {
return true;
}
return false;
}
async shouldRetry(err, diagnosticNode, retryContext, locationEndpoint, requestContext) {
if (!err) {
return false;
}
if (!retryContext || !locationEndpoint) {
return false;
}
if (!this.enableEndPointDiscovery) {
return false;
}
// Mark the partition as unavailable.
// Let the Retry logic decide if the request should be retried
if (requestContext && this.globalPartitionEndpointManager) {
await this.globalPartitionEndpointManager.tryPartitionLevelFailover(requestContext, diagnosticNode);
}
// Check if the error is a timeout error (TimeoutErrorCode) and if it is not a valid HTTP network timeout request
if (err.code === TimeoutErrorCode && !this.isValidRequestForTimeoutError()) {
return false;
}
if (err.code === StatusCodes.ServiceUnavailable &&
this.failoverRetryCount >= this.maxServiceUnavailableRetryCount) {
return false;
}
if (this.failoverRetryCount >= this.maxRetryAttemptCount) {
return false;
}
const canUseMultipleWriteLocations = this.globalEndpointManager.canUseMultipleWriteLocations(this.resourceType, this.operationType);
const readRequest = isReadRequest(this.operationType);
if (!canUseMultipleWriteLocations &&
!readRequest &&
!this.globalPartitionEndpointManager?.isPartitionLevelAutomaticFailoverEnabled()) {
// Write requests on single master cannot be retried if partition level failover is disabled.
// This means there are no other regions available to serve the writes.
return false;
}
this.failoverRetryCount++;
// Setting the retryLocationIndex to the next available location for retry.
// The retryLocationIndex is determined based on the failoverRetryCount, starting from zero.
retryContext.retryLocationServerIndex = await this.findEndpointIndex(this.failoverRetryCount);
diagnosticNode.addData({ successfulRetryPolicy: "timeout-failover" });
return true;
}
/**
* Determines index of endpoint to be used for retry based upon failoverRetryCount and avalable locations
* @param failoverRetryCount - count of failovers
* @returns
*/
async findEndpointIndex(failoverRetryCount) {
// count of preferred locations specified by user
const preferredLocationsCount = this.globalEndpointManager.preferredLocationsCount;
const readRequest = isReadRequest(this.operationType);
let endpointIndex = 0;
// If preferredLocationsCount is not zero, it indicates that the user has specified preferred locations.
if (preferredLocationsCount !== 0) {
// The endpointIndex is set based on the preferred location and the failover retry count.
endpointIndex = failoverRetryCount % preferredLocationsCount;
}
else {
// In the absence of preferred locations, the endpoint selection is based on the failover count and the number of available locations.
if (readRequest) {
const getReadEndpoints = await this.globalEndpointManager.getReadEndpoints();
if (getReadEndpoints && getReadEndpoints.length > 0) {
endpointIndex = failoverRetryCount % getReadEndpoints.length;
}
}
else {
const getWriteEndpoints = await this.globalEndpointManager.getWriteEndpoints();
if (getWriteEndpoints && getWriteEndpoints.length > 0) {
endpointIndex = failoverRetryCount % getWriteEndpoints.length;
}
}
}
return endpointIndex;
}
}
//# sourceMappingURL=timeoutFailoverRetryPolicy.js.map