Skip to content

Commit

Permalink
fix: Improve remote evaluation fetch retry logic (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
tyiuhc authored Jan 31, 2024
1 parent ed1403e commit 525cf0f
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 6 deletions.
2 changes: 1 addition & 1 deletion packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
"dependencies": {
"@amplitude/analytics-node": "^1.3.4",
"@amplitude/analytics-types": "^1.3.1",
"@amplitude/experiment-core": "^0.7.1"
"@amplitude/experiment-core": "^0.7.2"
}
}
23 changes: 18 additions & 5 deletions packages/node/src/remote/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { EvaluationApi, SdkEvaluationApi } from '@amplitude/experiment-core';
import {
EvaluationApi,
FetchError,
SdkEvaluationApi,
} from '@amplitude/experiment-core';

import { version as PACKAGE_VERSION } from '../../gen/version';
import { FetchHttpClient, WrapperClient } from '../transport/http';
Expand Down Expand Up @@ -65,10 +69,12 @@ export class RemoteEvaluationClient {
return await this.doFetch(user, this.config.fetchTimeoutMillis, options);
} catch (e) {
console.error('[Experiment] Fetch failed: ', e);
try {
return await this.retryFetch(user, options);
} catch (e) {
console.error(e);
if (this.shouldRetryFetch(e)) {
try {
return await this.retryFetch(user, options);
} catch (e) {
console.error(e);
}
}
throw e;
}
Expand Down Expand Up @@ -155,6 +161,13 @@ export class RemoteEvaluationClient {
console.debug(message, ...optionalParams);
}
}

private shouldRetryFetch(e: Error): boolean {
if (e instanceof FetchError) {
return e.statusCode < 400 || e.statusCode >= 500 || e.statusCode === 429;
}
return true;
}
}

/**
Expand Down
43 changes: 43 additions & 0 deletions packages/node/test/remote/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FetchError } from '@amplitude/experiment-core';
import { RemoteEvaluationClient } from 'src/remote/client';
import { ExperimentUser } from 'src/types/user';

Expand Down Expand Up @@ -56,3 +57,45 @@ test('ExperimentClient.fetch, v2 off returns default variant', async () => {
expect(variant.value).toBeUndefined();
expect(variant.metadata.default).toEqual(true);
});

describe('ExperimentClient.fetch, retry with different response codes', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test.each([
[300, 'Fetch Exception 300', 1],
[400, 'Fetch Exception 400', 0],
[429, 'Fetch Exception 429', 1],
[500, 'Fetch Exception 500', 1],
[0, 'Other Exception', 1],
])(
'responseCode=%p, errorMessage=%p, retryCalled=%p',
async (responseCode, errorMessage, retryCalled) => {
const client = new RemoteEvaluationClient(API_KEY, { fetchRetries: 1 });

jest
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn(RemoteEvaluationClient.prototype as any, 'doFetch')
.mockImplementation(async () => {
return new Promise<RemoteEvaluationClient>((_resolve, reject) => {
if (responseCode === 0) {
reject(new Error(errorMessage));
} else {
reject(new FetchError(responseCode, errorMessage));
}
});
});
const retryMock = jest.spyOn(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RemoteEvaluationClient.prototype as any,
'retryFetch',
);
try {
await client.fetch({ user_id: 'test_user' });
} catch (e) {
// catch error
}
expect(retryMock).toHaveBeenCalledTimes(retryCalled);
},
);
});

0 comments on commit 525cf0f

Please sign in to comment.