-
Notifications
You must be signed in to change notification settings - Fork 5.7k
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
Conversation
3c5f1ed
to
b74bc0d
Compare
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
877d14e
to
67cb0b0
Compare
There was a problem hiding this 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.
lib/plugins/aws/deployList/index.js
Outdated
.then(this.setBucketName) | ||
.then(this.listDeployments), | ||
'deploy:list:functions:log': () => BbPromise.bind(this).then(this.listFunctions), | ||
'before:deploy:list:log': async () => this.validate(), |
There was a problem hiding this comment.
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
lib/plugins/aws/deployList/index.js
Outdated
.then(this.displayFunctions); | ||
async listFunctions() { | ||
const funcs = await this.getFunctions(); | ||
const funcsVersions = await await this.getFunctionVersions(funcs); |
There was a problem hiding this comment.
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 () => { |
There was a problem hiding this comment.
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)
Additionally, as it touches sensitive parts, it'll be great if you can confirm that integration tests pass with this changes |
2ef0db7
to
1fc6bab
Compare
I've checked the proposed approach. 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. 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? |
Yes, but only after updates are merged to master
Yes, please check the docs: https://github.com/serverless/serverless/tree/master/test#aws-integration-tests |
@juanjoDiaz is this ready for re-review? If it's the case please re-request it (in reviewers box). Thank you! |
bf52e1a
to
522dcf8
Compare
522dcf8
to
29d0afb
Compare
29d0afb
to
c8e72a2
Compare
There was a problem hiding this 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
lib/plugins/aws/deployFunction.js
Outdated
|
||
this.serverless.service.provider.remoteFunctionData = result; | ||
return result; | ||
} catch (err) { |
There was a problem hiding this comment.
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 {
lib/plugins/aws/invokeLocal/index.js
Outdated
const contents = await fse.readFile(dockerfileCachePath); | ||
|
||
if (contents.toString() === dockerFileContent) { | ||
cacheEquals = this.checkDockerImage(imageName); |
There was a problem hiding this comment.
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;
lib/plugins/aws/deployFunction.js
Outdated
|
||
'deploy:function:packageFunction': () => | ||
'deploy:function:packageFunction': async () => |
There was a problem hiding this comment.
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
lib/plugins/aws/invoke.js
Outdated
.then(this.log), | ||
'invoke:invoke': async () => { | ||
await this.extendedValidate(); | ||
const invokationReply = await this.invoke(); |
There was a problem hiding this comment.
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())
lib/plugins/aws/invokeLocal/index.js
Outdated
return spawnExt('docker', ['version']).catch(() => { | ||
async checkDockerDaemonStatus() { | ||
try { | ||
return spawnExt('docker', ['version']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It misses await
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
Outdated
} | ||
}); | ||
|
||
mvn.on('exit', async (code, signal) => { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)));
} ...
});
lib/plugins/aws/logs.js
Outdated
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'); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
lib/utils/timers.js
Outdated
@@ -0,0 +1,9 @@ | |||
'use strict'; |
There was a problem hiding this comment.
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
)
c2ae55a
to
31c3f33
Compare
There was a problem hiding this 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
lib/plugins/aws/invokeLocal/index.js
Outdated
return [] | ||
.concat(this.options.env || []) | ||
.map(itm => itm.split('=')) | ||
.map(([varName, ...varValue]) => [varName, varValue.join('=')]) | ||
.reduce( | ||
(envVarsFromOptions, [varName, varValue]) => ({ | ||
...envVarsFromOptions, | ||
[varName]: varValue || '', | ||
}), | ||
{} | ||
); |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/plugins/aws/invokeLocal/index.js
Outdated
async ensurePackage() { | ||
if ( | ||
this.options['skip-package'] && | ||
(await fileExists( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
31c3f33
to
08570e4
Compare
08570e4
to
fa113ac
Compare
There was a problem hiding this 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)
fa113ac
to
cf9562a
Compare
There was a problem hiding this 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
lib/plugins/aws/invokeLocal/index.js
Outdated
return fse | ||
.access( | ||
async ensurePackage() { | ||
try { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
cf9562a
to
70e8331
Compare
70e8331
to
04234f8
Compare
There was a problem hiding this 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
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.