UNPKG

5.43 kBPlain TextView Raw
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License. See License.txt in the project root for license information.
3
4import { HttpOperationResponse } from "../httpOperationResponse";
5import * as utils from "../util/utils";
6import { WebResourceLike } from "../webResource";
7import {
8 BaseRequestPolicy,
9 RequestPolicy,
10 RequestPolicyFactory,
11 RequestPolicyOptionsLike,
12} from "./requestPolicy";
13
14export interface RetryData {
15 retryCount: number;
16 retryInterval: number;
17 error?: RetryError;
18}
19
20export interface RetryError extends Error {
21 message: string;
22 code?: string;
23 innerError?: RetryError;
24}
25
26export function systemErrorRetryPolicy(
27 retryCount?: number,
28 retryInterval?: number,
29 minRetryInterval?: number,
30 maxRetryInterval?: number
31): RequestPolicyFactory {
32 return {
33 create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => {
34 return new SystemErrorRetryPolicy(
35 nextPolicy,
36 options,
37 retryCount,
38 retryInterval,
39 minRetryInterval,
40 maxRetryInterval
41 );
42 },
43 };
44}
45
46/**
47 * @class
48 * Instantiates a new "ExponentialRetryPolicyFilter" instance.
49 *
50 * @constructor
51 * @param {number} retryCount The client retry count.
52 * @param {number} retryInterval The client retry interval, in milliseconds.
53 * @param {number} minRetryInterval The minimum retry interval, in milliseconds.
54 * @param {number} maxRetryInterval The maximum retry interval, in milliseconds.
55 */
56export class SystemErrorRetryPolicy extends BaseRequestPolicy {
57 retryCount: number;
58 retryInterval: number;
59 minRetryInterval: number;
60 maxRetryInterval: number;
61 DEFAULT_CLIENT_RETRY_INTERVAL = 1000 * 30;
62 DEFAULT_CLIENT_RETRY_COUNT = 3;
63 DEFAULT_CLIENT_MAX_RETRY_INTERVAL = 1000 * 90;
64 DEFAULT_CLIENT_MIN_RETRY_INTERVAL = 1000 * 3;
65
66 constructor(
67 nextPolicy: RequestPolicy,
68 options: RequestPolicyOptionsLike,
69 retryCount?: number,
70 retryInterval?: number,
71 minRetryInterval?: number,
72 maxRetryInterval?: number
73 ) {
74 super(nextPolicy, options);
75 this.retryCount = typeof retryCount === "number" ? retryCount : this.DEFAULT_CLIENT_RETRY_COUNT;
76 this.retryInterval =
77 typeof retryInterval === "number" ? retryInterval : this.DEFAULT_CLIENT_RETRY_INTERVAL;
78 this.minRetryInterval =
79 typeof minRetryInterval === "number"
80 ? minRetryInterval
81 : this.DEFAULT_CLIENT_MIN_RETRY_INTERVAL;
82 this.maxRetryInterval =
83 typeof maxRetryInterval === "number"
84 ? maxRetryInterval
85 : this.DEFAULT_CLIENT_MAX_RETRY_INTERVAL;
86 }
87
88 public sendRequest(request: WebResourceLike): Promise<HttpOperationResponse> {
89 return this._nextPolicy
90 .sendRequest(request.clone())
91 .catch((error) => retry(this, request, error.response, error));
92 }
93}
94
95/**
96 * Determines if the operation should be retried and how long to wait until the next retry.
97 *
98 * @param {number} statusCode The HTTP status code.
99 * @param {RetryData} retryData The retry data.
100 * @return {boolean} True if the operation qualifies for a retry; false otherwise.
101 */
102function shouldRetry(policy: SystemErrorRetryPolicy, retryData: RetryData): boolean {
103 let currentCount;
104 if (!retryData) {
105 throw new Error("retryData for the SystemErrorRetryPolicyFilter cannot be null.");
106 } else {
107 currentCount = retryData && retryData.retryCount;
108 }
109 return currentCount < policy.retryCount;
110}
111
112/**
113 * Updates the retry data for the next attempt.
114 *
115 * @param {RetryData} retryData The retry data.
116 * @param {object} err The operation"s error, if any.
117 */
118function updateRetryData(
119 policy: SystemErrorRetryPolicy,
120 retryData?: RetryData,
121 err?: RetryError
122): RetryData {
123 if (!retryData) {
124 retryData = {
125 retryCount: 0,
126 retryInterval: 0,
127 };
128 }
129
130 if (err) {
131 if (retryData.error) {
132 err.innerError = retryData.error;
133 }
134
135 retryData.error = err;
136 }
137
138 // Adjust retry count
139 retryData.retryCount++;
140
141 // Adjust retry interval
142 let incrementDelta = Math.pow(2, retryData.retryCount) - 1;
143 const boundedRandDelta =
144 policy.retryInterval * 0.8 + Math.floor(Math.random() * (policy.retryInterval * 0.4));
145 incrementDelta *= boundedRandDelta;
146
147 retryData.retryInterval = Math.min(
148 policy.minRetryInterval + incrementDelta,
149 policy.maxRetryInterval
150 );
151
152 return retryData;
153}
154
155async function retry(
156 policy: SystemErrorRetryPolicy,
157 request: WebResourceLike,
158 operationResponse: HttpOperationResponse,
159 err?: RetryError,
160 retryData?: RetryData
161): Promise<HttpOperationResponse> {
162 retryData = updateRetryData(policy, retryData, err);
163 if (
164 err &&
165 err.code &&
166 shouldRetry(policy, retryData) &&
167 (err.code === "ETIMEDOUT" ||
168 err.code === "ESOCKETTIMEDOUT" ||
169 err.code === "ECONNREFUSED" ||
170 err.code === "ECONNRESET" ||
171 err.code === "ENOENT")
172 ) {
173 // If previous operation ended with an error and the policy allows a retry, do that
174 try {
175 await utils.delay(retryData.retryInterval);
176 return policy._nextPolicy.sendRequest(request.clone());
177 } catch (error) {
178 return retry(policy, request, operationResponse, error, retryData);
179 }
180 } else {
181 if (err) {
182 // If the operation failed in the end, return all errors instead of just the last one
183 return Promise.reject(retryData.error);
184 }
185 return operationResponse;
186 }
187}