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

fix(serverless): wrapEventFunction does not await for async code #3740

Merged
merged 5 commits into from
Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 3 additions & 3 deletions packages/serverless/src/gcpfunction/cloud_events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ function _wrapCloudEventFunction(
return (fn as CloudEventFunctionWithCallback)(context, newCallback);
}

void Promise.resolve()
return Promise.resolve()
Copy link

@danieljimeneznz danieljimeneznz Jun 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this fix been tested on GCP using the example that @killthekitten provided in the example?

My only concern with this is that the top level function i.e. L37 is returning a non-asynchronous function to the GCP runner, so the promises within the function might not be correctly resolved within the runners lifecycle? (I might be wrong)

If my above concern is valid, it would be helpful to have the following expectations in the tests:

expect(wrappedHandler).toBeInstanceOf(Promise);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we tested it and it works.

.then(() => (fn as CloudEventFunction)(context))
.then(
result => {
newCallback(null, result);
return newCallback(null, result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we write result => newCallback(null, result) prettier should format it nicely

},
err => {
newCallback(err, undefined);
return newCallback(err, undefined);
},
);
};
Expand Down
6 changes: 3 additions & 3 deletions packages/serverless/src/gcpfunction/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ function _wrapEventFunction(
return (fn as EventFunctionWithCallback)(data, context, newCallback);
}

void Promise.resolve()
return Promise.resolve()
.then(() => (fn as EventFunction)(data, context))
.then(
result => {
newCallback(null, result);
return newCallback(null, result);
},
err => {
newCallback(err, undefined);
return newCallback(err, undefined);
},
);
};
Expand Down
43 changes: 43 additions & 0 deletions packages/serverless/test/gcpfunction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,49 @@ describe('GCPFunction', () => {
});
});

describe('wrapEventFunction() as Promise', () => {
test('successful execution', async () => {
expect.assertions(5);

const func: EventFunction = (_data, _context) =>
new Promise(resolve => {
setTimeout(() => {
resolve(42);
}, 10);
});
const wrappedHandler = wrapEventFunction(func);
await expect(handleEvent(wrappedHandler)).resolves.toBe(42);
expect(Sentry.startTransaction).toBeCalledWith({ name: 'event.type', op: 'gcp.function.event' });
// @ts-ignore see "Why @ts-ignore" note
expect(Sentry.fakeScope.setSpan).toBeCalledWith(Sentry.fakeTransaction);
// @ts-ignore see "Why @ts-ignore" note
expect(Sentry.fakeTransaction.finish).toBeCalled();
expect(Sentry.flush).toBeCalledWith(2000);
});

test('capture error', async () => {
expect.assertions(6);

const error = new Error('wat');
const handler: EventFunction = (_data, _context) =>
new Promise((_, reject) => {
setTimeout(() => {
reject(error);
}, 10);
});

const wrappedHandler = wrapEventFunction(handler);
await expect(handleEvent(wrappedHandler)).rejects.toThrowError(error);
expect(Sentry.startTransaction).toBeCalledWith({ name: 'event.type', op: 'gcp.function.event' });
// @ts-ignore see "Why @ts-ignore" note
expect(Sentry.fakeScope.setSpan).toBeCalledWith(Sentry.fakeTransaction);
expect(Sentry.captureException).toBeCalledWith(error);
// @ts-ignore see "Why @ts-ignore" note
expect(Sentry.fakeTransaction.finish).toBeCalled();
expect(Sentry.flush).toBeCalled();
});
});

describe('wrapEventFunction() with callback', () => {
test('successful execution', async () => {
expect.assertions(5);
Expand Down