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

Remove bluebird and use async/await from most of the AWS plugin #8593

Merged
merged 8 commits into from
Dec 16, 2020

Conversation

juanjoDiaz
Copy link
Contributor

Addresses: #7171

I might keep doing the same for other modules. But I think that it's better to merge small increments than wait for a full solution rewrite.

@juanjoDiaz juanjoDiaz force-pushed the remove_bluebird branch 3 times, most recently from 3c5f1ed to b74bc0d Compare December 9, 2020 09:57
@codecov-io
Copy link

codecov-io commented Dec 9, 2020

Codecov Report

Merging #8593 (04234f8) into master (d0beed4) will increase coverage by 0.02%.
The diff coverage is 67.32%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #8593      +/-   ##
==========================================
+ Coverage   86.94%   86.97%   +0.02%     
==========================================
  Files         252      252              
  Lines        9517     9505      -12     
==========================================
- Hits         8275     8267       -8     
+ Misses       1242     1238       -4     
Impacted Files Coverage Δ
lib/classes/Variables.js 99.72% <ø> (ø)
lib/plugins/aws/invokeLocal/index.js 68.53% <55.93%> (+0.10%) ⬆️
lib/plugins/aws/deployFunction.js 89.16% <72.97%> (+0.18%) ⬆️
lib/plugins/aws/logs.js 87.03% <75.00%> (-6.97%) ⬇️
lib/plugins/aws/deployList.js 91.66% <86.11%> (-1.89%) ⬇️
lib/plugins/aws/configCredentials.js 100.00% <100.00%> (ø)
lib/plugins/aws/invoke.js 97.82% <100.00%> (-0.05%) ⬇️
lib/plugins/aws/metrics.js 100.00% <100.00%> (ø)
lib/plugins/package/lib/zipService.js 96.29% <0.00%> (-0.04%) ⬇️
lib/plugins/package/lib/packageService.js 75.45% <0.00%> (+4.86%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d0beed4...04234f8. Read the comment docs.

Copy link
Contributor

@medikoo medikoo left a comment

Choose a reason for hiding this comment

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

Thank you @juanjoDiaz !

Concerning async/away adoption, we have planned a more careful step by step approach.
Check "Proposed solution" in description of #7171

Technically first planned step is #8368, and while there were volunteers it seems stalled, so you definitely can take this on.

Also we should not invest time into refactor of old test files, as they're anyway scheduled for greater refactor into runServerless variant (and that would trash most of the existing tests code)

Anyway work you've done looks very good and I'll be happy to take it. I've spotted just two minor things. See my comments.

.then(this.setBucketName)
.then(this.listDeployments),
'deploy:list:functions:log': () => BbPromise.bind(this).then(this.listFunctions),
'before:deploy:list:log': async () => this.validate(),
Copy link
Contributor

Choose a reason for hiding this comment

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

In single method calls we may skip async token. Sometimes it's a sync function that's invoked, and hooks accept both async and sync callbacks

.then(this.displayFunctions);
async listFunctions() {
const funcs = await this.getFunctions();
const funcsVersions = await await this.getFunctionVersions(funcs);
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's clear doubled await

@@ -20,7 +20,7 @@ describe('AwsDeployFunction', () => {
let awsDeployFunction;
let cryptoStub;

beforeEach(() => {
beforeEach(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This refactor in general looks good, but we should not invest time into refactor of test files, as most of them are scheduled for general refactor (check issues with test label as: #8594 - currently just for some of files they were created, but in a long run all files should be addressed)

@medikoo
Copy link
Contributor

medikoo commented Dec 9, 2020

Additionally, as it touches sensitive parts, it'll be great if you can confirm that integration tests pass with this changes

@juanjoDiaz
Copy link
Contributor Author

I've checked the proposed approach.
It could definitely work. I just did these modules since I was looking around the area.

Regarding the test, I had to do some minimal refactoring because making functions async made all error async and before there were some sync errors.
But I will not do any more refactoring of test.

I have few more AWS-related modules asyncified and I'll keep pushing them one by one to ensure that tests pass.

Are the integration tests run by GitHub actions?
Do they need real AWS credentials (i.e. do they actually deploy stacks)?

@medikoo
Copy link
Contributor

medikoo commented Dec 10, 2020

Are the integration tests run by GitHub actions?

Yes, but only after updates are merged to master

Do they need real AWS credentials (i.e. do they actually deploy stacks)?

Yes, please check the docs: https://github.com/serverless/serverless/tree/master/test#aws-integration-tests

@medikoo
Copy link
Contributor

medikoo commented Dec 14, 2020

@juanjoDiaz is this ready for re-review? If it's the case please re-request it (in reviewers box). Thank you!

Copy link
Contributor

@medikoo medikoo left a comment

Choose a reason for hiding this comment

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

Thanks @juanjoDiaz for update! It looks really promising. I've noticed a few issues, and suggested some minor style improvements


this.serverless.service.provider.remoteFunctionData = result;
return result;
} catch (err) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We do not handle err, so let's maybe write it as } catch {

const contents = await fse.readFile(dockerfileCachePath);

if (contents.toString() === dockerFileContent) {
cacheEquals = this.checkDockerImage(imageName);
Copy link
Contributor

Choose a reason for hiding this comment

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

It misses await, also instead of creating cacheEquals it'll be nice to simply do:

if (await this.checkDockerImage(imageName)) return imageName;


'deploy:function:packageFunction': () =>
'deploy:function:packageFunction': async () =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's drop async keyword here

.then(this.log),
'invoke:invoke': async () => {
await this.extendedValidate();
const invokationReply = await this.invoke();
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's simply write it as this.log(await this.invoke())

return spawnExt('docker', ['version']).catch(() => {
async checkDockerDaemonStatus() {
try {
return spawnExt('docker', ['version']);
Copy link
Contributor

Choose a reason for hiding this comment

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

It misses await

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When returning a promise, await is not needed. But I'll add it anyway.

Copy link
Contributor

@medikoo medikoo Dec 14, 2020

Choose a reason for hiding this comment

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

In try clause it is needed, otherwise eventual rejection won't be caught

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right! 🤦‍♂️

lib/plugins/aws/invokeLocal/index.js Show resolved Hide resolved
lib/plugins/aws/invokeLocal/index.js Show resolved Hide resolved
}
});

mvn.on('exit', async (code, signal) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

This handler should not be an async function as it's result is not processed (technically that creates an orphaned promise)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I disagree on this one.

And an orphaned promise is a promise that's never resolved so stays there taking up memory and event loop cycles.

This is just a promise that we don't care about the result.
It will be invoked by the internals of mvn.on and it will be garbage collected whenever it resolved and mvn is gc'd to.

The previous code was also using a promise which result was not processed (callJavaBridge)

            mvn.on('exit', (code, signal) => {
              if (code === 0) {
                this.callJavaBridge(artifactPath, className, handlerName, input).then(resolve);	        );
              } else if (!isRejected) {

We have plenty of promises that we don't process the results, like whenever we do await fs.lstat(file) to catch if the file exists.

Does that make sense?
Or would you still like me to make it sync?

Copy link
Contributor

Choose a reason for hiding this comment

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

And an orphaned promise is a promise that's never resolved

It is resolved, and if rejected, it'll end as unhandled rejection

The right way to do that is as follows:

mvn.on('exit', (code, signal) => {
  if (code === 0) {
    resolve(this.callJavaBridge(artifactPath, className, handlerName, input)));
  } ...
});

const _ = require('lodash');
const dayjs = require('dayjs');
const utc = require('dayjs/plugin/utc');
const validate = require('./lib/validate');
const formatLambdaLogEvent = require('./utils/formatLambdaLogEvent');
const timers = require('../../utils/timers');
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use timers-ext/sleep instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I missed that.

I tried to change it but it's not trivial since it's stubbed by the tests and I'm not sure how to stub function that's directly required as main export from a module (if possible).

Also, considering that the implementation of such timer is

async function sleep(interval = 0) {
  await new Promise(resolve => setTimeout(resolve, interval));
}

Do you think that's worth having a dependency just for that or could we remove the dep in a separate PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure how to stub function that's directly required as main export from a module (if possible).

We address such cases usually with proxyquire

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think that's worth having a dependency

yes, we prefer to rely on single toolkit served as external package (which is well backed by tests), instead of copying those utils in every package we find use for it

@@ -0,0 +1,9 @@
'use strict';
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not introduce that (we already use timers-ext/sleep)

Copy link
Contributor

@medikoo medikoo left a comment

Choose a reason for hiding this comment

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

Thank you @juanjoDiaz we're getting near! I have few final remarks and we should be good to go

Comment on lines 426 to 436
return []
.concat(this.options.env || [])
.map(itm => itm.split('='))
.map(([varName, ...varValue]) => [varName, varValue.join('=')])
.reduce(
(envVarsFromOptions, [varName, varValue]) => ({
...envVarsFromOptions,
[varName]: varValue || '',
}),
{}
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not use reduce here (this pattern is confusing, and this use case doesn't if it well).

Let's simply do something as:

const envVarsFromOptions = {};
for (const envOption of ...) {}
return envVarsFromOptions;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

async ensurePackage() {
if (
this.options['skip-package'] &&
(await fileExists(
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not equivalent. if fileExists crashes we need to issue spawn. With above construct it's not done, and that introduces bug

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fileExist can not crash because its implementation is:

  return fse
    .lstat(filePath)
    .then(stats => stats.isFile())
    .catch(() => false);

It is one of those extension methods that you have especially for this case.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's true, still my point was that we should maintain original logic (I overlooked you replaced fs.access with fileExists).

We should keepfse.access (it does a bit more that fse.exists).

Ideally this PR should be just about async/await refactor, nothing more (let's keep different concerns separated)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted to fse.access and previous logic.

Copy link
Contributor

@medikoo medikoo left a comment

Choose a reason for hiding this comment

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

As commented. This PR should be purely about async/await refactor, nothing more.

Let's discuss any other improvements in other issue (to avoid unnecessary work, let's have all ideas confirmed in issues, before jumping to PR's)

Copy link
Contributor

@medikoo medikoo left a comment

Choose a reason for hiding this comment

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

@juanjoDiaz thanks for update. I believe we just one minor thing left. and that should be good to take in

return fse
.access(
async ensurePackage() {
try {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not wrap if with try (ideally only logic parts we intend to catch errors from should be wrapped with try blocks)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

It's now a bit uglier because we need to duplicate the spawn but it's exactly the same logic.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's now a bit uglier because we need to duplicate the spawn but it's exactly the same logic.

No, there was no need to duplicate it. Your approach was perfect in terms that you didn't duplicate spawn, and it'll be great to maintain that.

My point was simply, that try/catch should be within if and not wrapping it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤦‍♂️

So much back and forth that I'm starting to miss the obvious....

Done!

Copy link
Contributor

@medikoo medikoo left a comment

Choose a reason for hiding this comment

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

Thank you @juanjoDiaz ! That's a great improvement. Code is definitely less confusing now

@medikoo medikoo merged commit 84d423d into serverless:master Dec 16, 2020
@juanjoDiaz juanjoDiaz deleted the remove_bluebird branch December 16, 2020 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants