Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: keep alive when dispatch fails #524

Merged
merged 7 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/dispatch/Dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class Dispatch {
private dispatchTimerId: number | undefined;
private buildClient: ClientBuilder;
private config: Config;
private disableCodes = ['401', '403', '404'];

constructor(
region: string,
Expand Down Expand Up @@ -232,10 +233,12 @@ export class Dispatch {
}

private handleReject = (e: any): { response: HttpResponse } => {
// The handler has run out of retries. We adhere to our convention to
// fail safe by disabling dispatch. This ensures that we will not
// continue to attempt requests when the problem is not recoverable.
this.disable();
if (e instanceof Error && this.disableCodes.includes(e.message)) {
// RUM disables only when dispatch fails and we are certain
// that subsequent attempts will not succeed, such as when
// credentials are invalid or the app monitor does not exist.
this.disable();
}
throw e;
};

Expand Down
138 changes: 128 additions & 10 deletions src/dispatch/__tests__/Dispatch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,15 +419,41 @@ describe('Dispatch tests', () => {
);
});

test('when a fetch request is rejected then dispatch is disabled', async () => {
test('when a fetch request is rejected with 429 then dispatch is NOT disabled', async () => {
// Init
const ERROR = 'Something went wrong.';
const sendFetch = jest.fn(() => Promise.reject(ERROR));
(DataPlaneClient as any).mockImplementation(() => {
return {
sendFetch
};
});
(DataPlaneClient as any).mockImplementationOnce(() => ({
sendFetch: () => Promise.reject(new Error('429'))
}));

const eventCache: EventCache =
Utils.createDefaultEventCacheWithEvents();

const dispatch = new Dispatch(
Utils.AWS_RUM_REGION,
Utils.AWS_RUM_ENDPOINT,
eventCache,
{
...DEFAULT_CONFIG,
...{ dispatchInterval: Utils.AUTO_DISPATCH_OFF, retries: 0 }
}
);
dispatch.setAwsCredentials(Utils.createAwsCredentials());

// Run
eventCache.recordEvent('com.amazon.rum.event1', {});

// Assert
await expect(dispatch.dispatchFetch()).rejects.toEqual(
new Error('429')
);
expect((dispatch as unknown as any).enabled).toBe(true);
});

test('when a fetch request is rejected with 500 then dispatch is NOT disabled', async () => {
// Init
(DataPlaneClient as any).mockImplementationOnce(() => ({
sendFetch: () => Promise.reject(new Error('500'))
}));

const eventCache: EventCache =
Utils.createDefaultEventCacheWithEvents();
Expand All @@ -444,11 +470,103 @@ describe('Dispatch tests', () => {
dispatch.setAwsCredentials(Utils.createAwsCredentials());

// Run
await expect(dispatch.dispatchFetch()).rejects.toEqual(ERROR);
eventCache.recordEvent('com.amazon.rum.event1', {});

// Assert
await expect(dispatch.dispatchFetch()).resolves.toEqual(undefined);
await expect(dispatch.dispatchFetch()).rejects.toEqual(
new Error('500')
);
expect((dispatch as unknown as any).enabled).toBe(true);
});

test('when a fetch request is rejected with 401 then dispatch is disabled', async () => {
// Init
(DataPlaneClient as any).mockImplementationOnce(() => ({
sendFetch: () => Promise.reject(new Error('401'))
}));

const eventCache: EventCache =
Utils.createDefaultEventCacheWithEvents();

const dispatch = new Dispatch(
Utils.AWS_RUM_REGION,
Utils.AWS_RUM_ENDPOINT,
eventCache,
{
...DEFAULT_CONFIG,
...{ dispatchInterval: Utils.AUTO_DISPATCH_OFF, retries: 0 }
}
);
dispatch.setAwsCredentials(Utils.createAwsCredentials());

// Run
eventCache.recordEvent('com.amazon.rum.event1', {});

// Assert
await expect(dispatch.dispatchFetch()).rejects.toEqual(
new Error('401')
);
expect((dispatch as unknown as any).enabled).toBe(false);
});

test('when a fetch request is rejected with 403 then dispatch is disabled', async () => {
// Init
(DataPlaneClient as any).mockImplementationOnce(() => ({
sendFetch: () => Promise.reject(new Error('403'))
}));

const eventCache: EventCache =
Utils.createDefaultEventCacheWithEvents();

const dispatch = new Dispatch(
Utils.AWS_RUM_REGION,
Utils.AWS_RUM_ENDPOINT,
eventCache,
{
...DEFAULT_CONFIG,
...{ dispatchInterval: Utils.AUTO_DISPATCH_OFF, retries: 0 }
}
);
dispatch.setAwsCredentials(Utils.createAwsCredentials());

// Run
eventCache.recordEvent('com.amazon.rum.event1', {});

// Assert
await expect(dispatch.dispatchFetch()).rejects.toEqual(
new Error('403')
);
expect((dispatch as unknown as any).enabled).toBe(false);
});

test('when a fetch request is rejected with 404 then dispatch is disabled', async () => {
// Init
(DataPlaneClient as any).mockImplementationOnce(() => ({
sendFetch: () => Promise.reject(new Error('404'))
}));

const eventCache: EventCache =
Utils.createDefaultEventCacheWithEvents();

const dispatch = new Dispatch(
Utils.AWS_RUM_REGION,
Utils.AWS_RUM_ENDPOINT,
eventCache,
{
...DEFAULT_CONFIG,
...{ dispatchInterval: Utils.AUTO_DISPATCH_OFF, retries: 0 }
}
);
dispatch.setAwsCredentials(Utils.createAwsCredentials());

// Run
eventCache.recordEvent('com.amazon.rum.event1', {});

// Assert
await expect(dispatch.dispatchFetch()).rejects.toEqual(
new Error('404')
);
expect((dispatch as unknown as any).enabled).toBe(false);
});

test('when signing is disabled then credentials are not needed for dispatch', async () => {
Expand Down
Loading