systemErrorRetryPolicy.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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. import { HttpOperationResponse } from "../httpOperationResponse";
  4. import * as utils from "../util/utils";
  5. import { WebResource } from "../webResource";
  6. import { BaseRequestPolicy, RequestPolicy, RequestPolicyFactory, RequestPolicyOptions } from "./requestPolicy";
  7. export interface RetryData {
  8. retryCount: number;
  9. retryInterval: number;
  10. error?: RetryError;
  11. }
  12. export interface RetryError extends Error {
  13. message: string;
  14. code?: string;
  15. innerError?: RetryError;
  16. }
  17. export function systemErrorRetryPolicy(retryCount?: number, retryInterval?: number, minRetryInterval?: number, maxRetryInterval?: number): RequestPolicyFactory {
  18. return {
  19. create: (nextPolicy: RequestPolicy, options: RequestPolicyOptions) => {
  20. return new SystemErrorRetryPolicy(nextPolicy, options, retryCount, retryInterval, minRetryInterval, maxRetryInterval);
  21. }
  22. };
  23. }
  24. /**
  25. * @class
  26. * Instantiates a new "ExponentialRetryPolicyFilter" instance.
  27. *
  28. * @constructor
  29. * @param {number} retryCount The client retry count.
  30. * @param {number} retryInterval The client retry interval, in milliseconds.
  31. * @param {number} minRetryInterval The minimum retry interval, in milliseconds.
  32. * @param {number} maxRetryInterval The maximum retry interval, in milliseconds.
  33. */
  34. export class SystemErrorRetryPolicy extends BaseRequestPolicy {
  35. retryCount: number;
  36. retryInterval: number;
  37. minRetryInterval: number;
  38. maxRetryInterval: number;
  39. DEFAULT_CLIENT_RETRY_INTERVAL = 1000 * 30;
  40. DEFAULT_CLIENT_RETRY_COUNT = 3;
  41. DEFAULT_CLIENT_MAX_RETRY_INTERVAL = 1000 * 90;
  42. DEFAULT_CLIENT_MIN_RETRY_INTERVAL = 1000 * 3;
  43. constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptions, retryCount?: number, retryInterval?: number, minRetryInterval?: number, maxRetryInterval?: number) {
  44. super(nextPolicy, options);
  45. this.retryCount = typeof retryCount === "number" ? retryCount : this.DEFAULT_CLIENT_RETRY_COUNT;
  46. this.retryInterval = typeof retryInterval === "number" ? retryInterval : this.DEFAULT_CLIENT_RETRY_INTERVAL;
  47. this.minRetryInterval = typeof minRetryInterval === "number" ? minRetryInterval : this.DEFAULT_CLIENT_MIN_RETRY_INTERVAL;
  48. this.maxRetryInterval = typeof maxRetryInterval === "number" ? maxRetryInterval : this.DEFAULT_CLIENT_MAX_RETRY_INTERVAL;
  49. }
  50. public sendRequest(request: WebResource): Promise<HttpOperationResponse> {
  51. return this._nextPolicy.sendRequest(request.clone()).then(response => retry(this, request, response));
  52. }
  53. }
  54. /**
  55. * Determines if the operation should be retried and how long to wait until the next retry.
  56. *
  57. * @param {number} statusCode The HTTP status code.
  58. * @param {RetryData} retryData The retry data.
  59. * @return {boolean} True if the operation qualifies for a retry; false otherwise.
  60. */
  61. function shouldRetry(policy: SystemErrorRetryPolicy, retryData: RetryData): boolean {
  62. let currentCount;
  63. if (!retryData) {
  64. throw new Error("retryData for the SystemErrorRetryPolicyFilter cannot be null.");
  65. } else {
  66. currentCount = (retryData && retryData.retryCount);
  67. }
  68. return (currentCount < policy.retryCount);
  69. }
  70. /**
  71. * Updates the retry data for the next attempt.
  72. *
  73. * @param {RetryData} retryData The retry data.
  74. * @param {object} err The operation"s error, if any.
  75. */
  76. function updateRetryData(policy: SystemErrorRetryPolicy, retryData?: RetryData, err?: RetryError): RetryData {
  77. if (!retryData) {
  78. retryData = {
  79. retryCount: 0,
  80. retryInterval: 0
  81. };
  82. }
  83. if (err) {
  84. if (retryData.error) {
  85. err.innerError = retryData.error;
  86. }
  87. retryData.error = err;
  88. }
  89. // Adjust retry count
  90. retryData.retryCount++;
  91. // Adjust retry interval
  92. let incrementDelta = Math.pow(2, retryData.retryCount) - 1;
  93. const boundedRandDelta = policy.retryInterval * 0.8 +
  94. Math.floor(Math.random() * (policy.retryInterval * 1.2 - policy.retryInterval * 0.8));
  95. incrementDelta *= boundedRandDelta;
  96. retryData.retryInterval = Math.min(policy.minRetryInterval + incrementDelta, policy.maxRetryInterval);
  97. return retryData;
  98. }
  99. function retry(policy: SystemErrorRetryPolicy, request: WebResource, operationResponse: HttpOperationResponse, retryData?: RetryData, err?: RetryError): Promise<HttpOperationResponse> {
  100. retryData = updateRetryData(policy, retryData, err);
  101. if (err && err.code && shouldRetry(policy, retryData) &&
  102. (err.code === "ETIMEDOUT" || err.code === "ESOCKETTIMEDOUT" || err.code === "ECONNREFUSED" ||
  103. err.code === "ECONNRESET" || err.code === "ENOENT")) {
  104. // If previous operation ended with an error and the policy allows a retry, do that
  105. return utils.delay(retryData.retryInterval)
  106. .then(() => policy._nextPolicy.sendRequest(request.clone()))
  107. .then(res => retry(policy, request, res, retryData, err))
  108. .catch(err => retry(policy, request, operationResponse, retryData, err));
  109. } else {
  110. if (err != undefined) {
  111. // If the operation failed in the end, return all errors instead of just the last one
  112. err = retryData.error;
  113. return Promise.reject(err);
  114. }
  115. return Promise.resolve(operationResponse);
  116. }
  117. }