Skip to content
Merged
2 changes: 1 addition & 1 deletion sdk/core/core-amqp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// <reference lib="es2015" />

export { RequestResponseLink, SendRequestOptions } from "./requestResponseLink";
export { retry, RetryConfig, RetryOperationType } from "./retry";
export { retry, RetryConfig, RetryOperationType, RetryPolicy } from "./retry";
export { DataTransformer, DefaultDataTransformer } from "./dataTransformer";
export { TokenType } from "./auth/token";
export { AccessToken, TokenCredential, isTokenCredential } from "@azure/core-auth";
Expand Down
69 changes: 61 additions & 8 deletions sdk/core/core-amqp/src/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import { translate, MessagingError } from "./errors";
import { delay, isNode } from "./util/utils";
import * as log from "./log";
import { defaultMaxRetries, defaultDelayBetweenRetriesInSeconds } from "./util/constants";
import {
defaultMaxRetries,
defaultDelayBetweenOperationRetriesInSeconds,
defaultMaxDelayForExponentialRetryInMs,
defaultMinDelayForExponentialRetryInMs
} from "./util/constants";
import { resolve } from "dns";

/**
Expand All @@ -25,6 +30,15 @@ function isDelivery(obj: any): boolean {
return result;
}

/**
* Describes the RetryPolicy type
* @enum RetryPolicy
*/
export enum RetryPolicy {
ExponentialRetryPolicy,
LinearRetryPolicy
}

/**
* Describes the retry operation type.
* @enum RetryOperationType
Expand Down Expand Up @@ -66,14 +80,30 @@ export interface RetryConfig<T> {
maxRetries?: number;
/**
* @property {number} [delayInSeconds] Amount of time to wait in seconds before making the
* next attempt. Default: 15.
* next attempt. Default: 30.
* When `retryPolicy` option is set to `ExponentialRetryPolicy`, \
* this is used to compute the exponentially increasing delays between retries.
*/
delayInSeconds?: number;
/**
* @property {string} connectionHost The host "<yournamespace>.servicebus.windows.net".
* Used to check network connectivity.
*/
connectionHost?: string;
/**
* @property {RetryPolicy} [retryPolicy] Denotes which retry policy to apply. Default is `LinearRetryPolicy`
*/
retryPolicy?: RetryPolicy;
/**
* @property {number} [maxExponentialRetryDelayInMs] Denotes the maximum delay between retries
* that the retry attempts will be capped at. Applicable only when performing exponential retry.
*/
maxExponentialRetryDelayInMs?: number;
/**
* @property {number} [minExponentialRetryDelayInMs] Denotes the minimum delay between retries
* to use. Applicable only when performing exponential retry.
*/
minExponentialRetryDelayInMs?: number;
}

/**
Expand Down Expand Up @@ -115,10 +145,13 @@ async function checkNetworkConnection(host: string): Promise<boolean> {
* The number of additional attempts is governed by the `maxRetries` property provided
* on the `RetryConfig` argument.
*
* The retries when made are done so linearly on the given operation for a specified number of times,
* with a specified delay in between each retry.
* If `retryPolicy` option is set to `LinearRetryPolicy`, then the retries when made are done so linearly on the
* given operation for a specified number of times, with a specified delay in between each retry.
*
* If `retryPolicy` option is set to `ExponentialRetryPolicy`, then the delay between retries is adjusted to increase
* exponentially with each attempt using back-off factor of power 2.
*
* @param {RetryConfig<T>} config Parameters to configure retry operation.
* @param {RetryConfig<T>} config Parameters to configure retry operation
*
* @return {Promise<T>} Promise<T>.
*/
Expand All @@ -128,7 +161,13 @@ export async function retry<T>(config: RetryConfig<T>): Promise<T> {
config.maxRetries = defaultMaxRetries;
}
if (config.delayInSeconds == undefined || config.delayInSeconds < 0) {
config.delayInSeconds = defaultDelayBetweenRetriesInSeconds;
config.delayInSeconds = defaultDelayBetweenOperationRetriesInSeconds;
}
if (config.maxExponentialRetryDelayInMs == undefined || config.maxExponentialRetryDelayInMs < 0) {
config.maxExponentialRetryDelayInMs = defaultMaxDelayForExponentialRetryInMs;
}
if (config.minExponentialRetryDelayInMs == undefined || config.minExponentialRetryDelayInMs < 0) {
config.minExponentialRetryDelayInMs = defaultMinDelayForExponentialRetryInMs;
}
let lastError: MessagingError | undefined;
let result: any;
Expand Down Expand Up @@ -174,14 +213,28 @@ export async function retry<T>(config: RetryConfig<T>): Promise<T> {
i,
err
);
let targetDelayInMs = config.delayInSeconds;
if (config.retryPolicy === RetryPolicy.ExponentialRetryPolicy) {
let incrementDelta = Math.pow(2, i) - 1;
Copy link
Copy Markdown
Contributor

@ramya-rao-a ramya-rao-a Jul 6, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At a quick glance, the words minExponentialRetryDelayInMs and maxExponentialRetryDelayInMs tell me that when using exponential retry policy, the delay between retries starts from the minExponentialRetryDelayInMs and is incremented exponentially till maxExponentialRetryDelayInMs

This understanding holds true only if incrementDelta is initialized to Math.pow(2, i - 1) - 1 instead of Math.pow(2, i) - 1

But, I am not sure if this what we want it to mean :)
i.e I am not sure if the first delay interval should be minExponentialRetryDelayInMs or minExponentialRetryDelayInMs + (a random value between 0 and 1 * delayInSeconds)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolution to this is not a blocker for merging this PR, but is a blocker in closing the linked issue

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with your intuition here - the first time we retry, incrementDelta should be zero, and since we start on "try 1", Math.pow(2, i - 1) - 1 makes sense to me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case minExponentialRetryDelayInMs would need a better default ... Logged #4201 to discuss this

const boundedRandDelta =
config.delayInSeconds * 0.8 +
Math.floor(Math.random() * (config.delayInSeconds * 1.2 - config.delayInSeconds * 0.8));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't (config.delayInSeconds * 1.2 - config.delayInSeconds * 0.8) the same as configDelayInSeconds * 0.4?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, maybe the magic numbers/factors have a certain meaning? For instance we have time in milliseconds (say for 30 sec) being expressed as 1000 * 30 in some places for readability.

incrementDelta *= boundedRandDelta;

targetDelayInMs = Math.min(
config.minExponentialRetryDelayInMs + incrementDelta,
config.maxExponentialRetryDelayInMs
);
}

if (lastError && lastError.retryable) {
log.error(
"[%s] Sleeping for %d seconds for '%s'.",
config.connectionId,
config.delayInSeconds,
targetDelayInMs / 1000,
config.operationType
);
await delay(config.delayInSeconds * 1000);
await delay(targetDelayInMs);
continue;
} else {
break;
Expand Down
5 changes: 3 additions & 2 deletions sdk/core/core-amqp/src/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ export const aadTokenValidityMarginSeconds = 5;
export const connectionReconnectDelay = 300;
export const defaultMaxRetries = 3;
export const defaultMaxRetriesForConnection = 150;
export const defaultDelayBetweenOperationRetriesInSeconds = 5;
export const defaultDelayBetweenRetriesInSeconds = 15;
export const defaultDelayBetweenOperationRetriesInSeconds = 30;
export const defaultMaxDelayForExponentialRetryInMs = 1000 * 90;
export const defaultMinDelayForExponentialRetryInMs = 1000 * 3;
export const receiverSettleMode = "receiver-settle-mode";
export const dispositionStatus = "disposition-status";
export const fromSequenceNumber = "from-sequence-number";
Expand Down
Loading