1 |
|
2 |
|
3 |
|
4 | import { HttpOperationResponse } from "../httpOperationResponse";
|
5 | import * as utils from "../util/utils";
|
6 | import { WebResourceLike } from "../webResource";
|
7 | import {
|
8 | BaseRequestPolicy,
|
9 | RequestPolicy,
|
10 | RequestPolicyFactory,
|
11 | RequestPolicyOptionsLike,
|
12 | } from "./requestPolicy";
|
13 | import { RestError } from "../restError";
|
14 |
|
15 | export interface RetryData {
|
16 | retryCount: number;
|
17 | retryInterval: number;
|
18 | error?: RetryError;
|
19 | }
|
20 |
|
21 | export interface RetryError extends Error {
|
22 | message: string;
|
23 | code?: string;
|
24 | innerError?: RetryError;
|
25 | }
|
26 |
|
27 | export function exponentialRetryPolicy(
|
28 | retryCount?: number,
|
29 | retryInterval?: number,
|
30 | minRetryInterval?: number,
|
31 | maxRetryInterval?: number
|
32 | ): RequestPolicyFactory {
|
33 | return {
|
34 | create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => {
|
35 | return new ExponentialRetryPolicy(
|
36 | nextPolicy,
|
37 | options,
|
38 | retryCount,
|
39 | retryInterval,
|
40 | minRetryInterval,
|
41 | maxRetryInterval
|
42 | );
|
43 | },
|
44 | };
|
45 | }
|
46 |
|
47 | const DEFAULT_CLIENT_RETRY_INTERVAL = 1000 * 30;
|
48 | const DEFAULT_CLIENT_RETRY_COUNT = 3;
|
49 | const DEFAULT_CLIENT_MAX_RETRY_INTERVAL = 1000 * 90;
|
50 | const DEFAULT_CLIENT_MIN_RETRY_INTERVAL = 1000 * 3;
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | export class ExponentialRetryPolicy extends BaseRequestPolicy {
|
57 | |
58 |
|
59 |
|
60 | retryCount: number;
|
61 | |
62 |
|
63 |
|
64 | retryInterval: number;
|
65 | |
66 |
|
67 |
|
68 | minRetryInterval: number;
|
69 | |
70 |
|
71 |
|
72 | maxRetryInterval: number;
|
73 |
|
74 | |
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 | constructor(
|
84 | nextPolicy: RequestPolicy,
|
85 | options: RequestPolicyOptionsLike,
|
86 | retryCount?: number,
|
87 | retryInterval?: number,
|
88 | minRetryInterval?: number,
|
89 | maxRetryInterval?: number
|
90 | ) {
|
91 | super(nextPolicy, options);
|
92 | function isNumber(n: any): n is number {
|
93 | return typeof n === "number";
|
94 | }
|
95 | this.retryCount = isNumber(retryCount) ? retryCount : DEFAULT_CLIENT_RETRY_COUNT;
|
96 | this.retryInterval = isNumber(retryInterval) ? retryInterval : DEFAULT_CLIENT_RETRY_INTERVAL;
|
97 | this.minRetryInterval = isNumber(minRetryInterval)
|
98 | ? minRetryInterval
|
99 | : DEFAULT_CLIENT_MIN_RETRY_INTERVAL;
|
100 | this.maxRetryInterval = isNumber(maxRetryInterval)
|
101 | ? maxRetryInterval
|
102 | : DEFAULT_CLIENT_MAX_RETRY_INTERVAL;
|
103 | }
|
104 |
|
105 | public sendRequest(request: WebResourceLike): Promise<HttpOperationResponse> {
|
106 | return this._nextPolicy
|
107 | .sendRequest(request.clone())
|
108 | .then((response) => retry(this, request, response))
|
109 | .catch((error) => retry(this, request, error.response, undefined, error));
|
110 | }
|
111 | }
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | function shouldRetry(
|
122 | policy: ExponentialRetryPolicy,
|
123 | statusCode: number | undefined,
|
124 | retryData: RetryData
|
125 | ): boolean {
|
126 | if (
|
127 | statusCode == undefined ||
|
128 | (statusCode < 500 && statusCode !== 408) ||
|
129 | statusCode === 501 ||
|
130 | statusCode === 505
|
131 | ) {
|
132 | return false;
|
133 | }
|
134 |
|
135 | let currentCount: number;
|
136 | if (!retryData) {
|
137 | throw new Error("retryData for the ExponentialRetryPolicyFilter cannot be null.");
|
138 | } else {
|
139 | currentCount = retryData && retryData.retryCount;
|
140 | }
|
141 |
|
142 | return currentCount < policy.retryCount;
|
143 | }
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 | function updateRetryData(
|
153 | policy: ExponentialRetryPolicy,
|
154 | retryData?: RetryData,
|
155 | err?: RetryError
|
156 | ): RetryData {
|
157 | if (!retryData) {
|
158 | retryData = {
|
159 | retryCount: 0,
|
160 | retryInterval: 0,
|
161 | };
|
162 | }
|
163 |
|
164 | if (err) {
|
165 | if (retryData.error) {
|
166 | err.innerError = retryData.error;
|
167 | }
|
168 |
|
169 | retryData.error = err;
|
170 | }
|
171 |
|
172 |
|
173 | retryData.retryCount++;
|
174 |
|
175 |
|
176 | let incrementDelta = Math.pow(2, retryData.retryCount) - 1;
|
177 | const boundedRandDelta =
|
178 | policy.retryInterval * 0.8 +
|
179 | Math.floor(Math.random() * (policy.retryInterval * 1.2 - policy.retryInterval * 0.8));
|
180 | incrementDelta *= boundedRandDelta;
|
181 |
|
182 | retryData.retryInterval = Math.min(
|
183 | policy.minRetryInterval + incrementDelta,
|
184 | policy.maxRetryInterval
|
185 | );
|
186 |
|
187 | return retryData;
|
188 | }
|
189 |
|
190 | function retry(
|
191 | policy: ExponentialRetryPolicy,
|
192 | request: WebResourceLike,
|
193 | response?: HttpOperationResponse,
|
194 | retryData?: RetryData,
|
195 | requestError?: RetryError
|
196 | ): Promise<HttpOperationResponse> {
|
197 | retryData = updateRetryData(policy, retryData, requestError);
|
198 | const isAborted: boolean | undefined = request.abortSignal && request.abortSignal.aborted;
|
199 | if (!isAborted && shouldRetry(policy, response && response.status, retryData)) {
|
200 | return utils
|
201 | .delay(retryData.retryInterval)
|
202 | .then(() => policy._nextPolicy.sendRequest(request.clone()))
|
203 | .then((res) => retry(policy, request, res, retryData, undefined))
|
204 | .catch((err) => retry(policy, request, response, retryData, err));
|
205 | } else if (isAborted || requestError || !response) {
|
206 |
|
207 | const err =
|
208 | retryData.error ||
|
209 | new RestError(
|
210 | "Failed to send the request.",
|
211 | RestError.REQUEST_SEND_ERROR,
|
212 | response && response.status,
|
213 | response && response.request,
|
214 | response
|
215 | );
|
216 | return Promise.reject(err);
|
217 | } else {
|
218 | return Promise.resolve(response);
|
219 | }
|
220 | }
|