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 |
|
14 | export interface RetryData {
|
15 | retryCount: number;
|
16 | retryInterval: number;
|
17 | error?: RetryError;
|
18 | }
|
19 |
|
20 | export interface RetryError extends Error {
|
21 | message: string;
|
22 | code?: string;
|
23 | innerError?: RetryError;
|
24 | }
|
25 |
|
26 | export 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 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | export 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 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 | function 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 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 | function 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 |
|
139 | retryData.retryCount++;
|
140 |
|
141 |
|
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 |
|
155 | async 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 |
|
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 |
|
183 | return Promise.reject(retryData.error);
|
184 | }
|
185 | return operationResponse;
|
186 | }
|
187 | }
|