Skip to content

Commit

Permalink
feat(node): exponential retry of requests on failure
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Feb 7, 2023
1 parent 0bec5b5 commit aadec0d
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import BigNumber from 'bignumber.js';
import { OperationArguments, OperationSpec } from '@azure/core-client';
import {
genRequestQueuesPolicy, genCombineGetRequestsPolicy, genErrorFormatterPolicy,
genVersionCheckPolicy,
genVersionCheckPolicy, genRetryOnFailurePolicy,
} from './utils/autorest';
import { Node as NodeApi, NodeOptionalParams, ErrorModel } from './apis/node';
import { mapObject } from './utils/other';
Expand Down Expand Up @@ -128,6 +128,7 @@ export default class Node extends (NodeTransformed as unknown as NodeTransformed
additionalPolicies: [
genRequestQueuesPolicy(),
genCombineGetRequestsPolicy(),
genRetryOnFailurePolicy(),
genErrorFormatterPolicy((body: ErrorModel) => ` ${body.reason}`),
],
...options,
Expand Down
29 changes: 28 additions & 1 deletion src/utils/autorest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RestError, PipelineResponse, PipelinePolicy } from '@azure/core-rest-pi
import { AdditionalPolicyConfig } from '@azure/core-client';
import { pause } from './other';
import semverSatisfies from './semver-satisfies';
import { UnsupportedVersionError } from './errors';
import { UnexpectedTsError, UnsupportedVersionError } from './errors';

export const genRequestQueuesPolicy = (): AdditionalPolicyConfig => {
const requestQueues = new Map<string, Promise<unknown>>();
Expand Down Expand Up @@ -106,3 +106,30 @@ export const genVersionCheckPolicy = (
return next(request);
},
});

export const genRetryOnFailurePolicy = (): AdditionalPolicyConfig => ({
policy: {
name: 'retry-on-failure',
async sendRequest(request, next) {
const statusesToNotRetry = [200, 400, 403];
let error: Error | undefined;
for (let attempt = 0; attempt <= 3; attempt += 1) {
if (error != null) {
if (
!(error instanceof RestError)
|| statusesToNotRetry.includes(error.response?.status ?? 0)
) throw error;
await pause((attempt / 3) ** 2 * 500);
}
try {
return await next(request);
} catch (e) {
error = e;
}
}
if (error == null) throw new UnexpectedTsError();
throw error;
},
},
position: 'perCall',
});
13 changes: 13 additions & 0 deletions test/integration/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import { describe, it, before } from 'mocha';
import { expect } from 'chai';
import { spy } from 'sinon';
import http from 'http';
import { url, ignoreVersion } from '.';
import { AeSdkBase, Node, NodeNotFoundError } from '../../src';

Expand All @@ -43,6 +45,17 @@ describe('Node client', () => {
.to.be.rejectedWith('v3/transactions/th_test error: Invalid hash');
});

it('retries requests if failed', async () => ([
['ak_test', 1],
['ak_2CxRaRcMUGn9s5UwN36UhdrtZVFUbgG1BSX5tUAyQbCNneUwti', 4],
] as const).reduce(async (prev, [address, requestCount]) => {
await prev;
const httpSpy = spy(http, 'request');
await node.getAccountByPubkey(address).catch(() => {});
expect(httpSpy.callCount).to.be.equal(requestCount);
httpSpy.restore();
}, Promise.resolve()));

describe('Node Pool', () => {
it('Throw error on using API without node', () => {
const nodes = new AeSdkBase({});
Expand Down

0 comments on commit aadec0d

Please sign in to comment.