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

ESLint: Require await inside async functions #5263

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

watson
Copy link
Collaborator

@watson watson commented Feb 13, 2025

What does this PR do?

Enable the require-await ESLint rule. The rule isn't enabled in tests, where the extra created promises doesn't harm anyone.

Motivation

If a function is marked as async, V8 will create a few extra promises behind the scenes when awaiting it. If the function doesn't itself contain any awaits, these extra promises just adds overhead that isn't needed.

Plugin Checklist

Additional Notes

Be aware: This might break some things that expect the function to return a promise. We'll have to go over each of the places where the PR removes the async keyword and check if the function returns something that should be a promise, but isn't.

There might be a few places, especially places were we use shimmer.wrap where we might actually want to keep the async keyword!

@watson watson requested review from a team as code owners February 13, 2025 12:56
@watson watson requested a review from tonyredondo February 13, 2025 12:56
Copy link
Collaborator Author

watson commented Feb 13, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@watson watson self-assigned this Feb 13, 2025
Copy link

github-actions bot commented Feb 13, 2025

Overall package size

Self size: 8.75 MB
Deduped: 94.95 MB
No deduping: 95.47 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | @datadog/libdatadog | 0.4.0 | 29.44 MB | 29.44 MB | | @datadog/native-appsec | 8.4.0 | 19.25 MB | 19.26 MB | | @datadog/native-iast-taint-tracking | 3.3.0 | 13.77 MB | 13.78 MB | | @datadog/pprof | 5.5.1 | 9.79 MB | 10.17 MB | | protobufjs | 7.2.5 | 2.77 MB | 5.16 MB | | @datadog/native-iast-rewriter | 2.8.0 | 2.6 MB | 2.74 MB | | @opentelemetry/core | 1.14.0 | 872.87 kB | 1.47 MB | | @datadog/native-metrics | 3.1.0 | 1.06 MB | 1.46 MB | | @opentelemetry/api | 1.8.0 | 1.21 MB | 1.21 MB | | import-in-the-middle | 1.11.2 | 112.74 kB | 835.4 kB | | source-map | 0.7.4 | 226 kB | 226 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | lru-cache | 7.18.3 | 133.92 kB | 133.92 kB | | pprof-format | 2.1.0 | 111.69 kB | 111.69 kB | | @datadog/sketches-js | 2.1.0 | 109.9 kB | 109.9 kB | | lodash.sortby | 4.7.0 | 75.76 kB | 75.76 kB | | ignore | 5.3.2 | 53.63 kB | 53.63 kB | | shell-quote | 1.8.1 | 44.96 kB | 44.96 kB | | istanbul-lib-coverage | 3.2.0 | 29.34 kB | 29.34 kB | | rfdc | 1.3.1 | 25.21 kB | 25.21 kB | | @isaacs/ttlcache | 1.4.1 | 25.2 kB | 25.2 kB | | tlhunter-sorted-set | 0.1.0 | 24.94 kB | 24.94 kB | | limiter | 1.1.5 | 23.17 kB | 23.17 kB | | dc-polyfill | 0.1.4 | 23.1 kB | 23.1 kB | | retry | 0.13.1 | 18.85 kB | 18.85 kB | | semifies | 1.0.0 | 15.84 kB | 15.84 kB | | jest-docblock | 29.7.0 | 8.99 kB | 12.76 kB | | crypto-randomuuid | 1.0.0 | 11.18 kB | 11.18 kB | | ttl-set | 1.0.0 | 4.61 kB | 9.69 kB | | path-to-regexp | 0.1.12 | 6.6 kB | 6.6 kB | | koalas | 1.0.2 | 6.47 kB | 6.47 kB | | module-details-from-path | 1.0.3 | 4.47 kB | 4.47 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

Copy link

codecov bot commented Feb 13, 2025

Codecov Report

Attention: Patch coverage is 0% with 1 line in your changes missing coverage. Please review.

Project coverage is 80.91%. Comparing base (172bc66) to head (8a0931a).

Files with missing lines Patch % Lines
...ackages/datadog-instrumentations/src/mocha/main.js 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5263      +/-   ##
==========================================
+ Coverage   80.80%   80.91%   +0.11%     
==========================================
  Files         487      488       +1     
  Lines       21710    21817     +107     
==========================================
+ Hits        17542    17654     +112     
+ Misses       4168     4163       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@datadog-datadog-prod-us1
Copy link

datadog-datadog-prod-us1 bot commented Feb 13, 2025

Datadog Report

Branch report: watson/eslint-require-await
Commit report: fe0c3c3
Test service: dd-trace-js-integration-tests

✅ 0 Failed, 663 Passed, 0 Skipped, 10m 33.82s Total Time

@pr-commenter
Copy link

pr-commenter bot commented Feb 13, 2025

Benchmarks

Benchmark execution time: 2025-02-19 06:34:00

Comparing candidate commit 8a0931a in PR branch watson/eslint-require-await with baseline commit 172bc66 in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 918 metrics, 15 unstable metrics.

IlyasShabi
IlyasShabi previously approved these changes Feb 13, 2025
@rochdev
Copy link
Member

rochdev commented Feb 15, 2025

Actually, can we instead have a rule to block async/await entirely? Promises too. We've had a non-written rule about never using promises in the codebase since day 1 because it can slow down async_hooks significantly.

@watson
Copy link
Collaborator Author

watson commented Feb 15, 2025

@rochdev I wouldn't mind doing that, but I feel that's a different PR

Copy link
Collaborator

@bengl bengl left a comment

Choose a reason for hiding this comment

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

LGTM except that one request change, and I'd like to make sure @juan-fernandez reviews the test-framework instrumentation code.

@watson watson force-pushed the watson/eslint-require-await branch from d78565a to 25835a5 Compare February 18, 2025 15:01
BridgeAR
BridgeAR previously approved these changes Feb 18, 2025
Copy link

@BridgeAR BridgeAR left a comment

Choose a reason for hiding this comment

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

I used the PR to familiarize myself with a bit more of the code and I looked very deeply into each usage. All of these changes are LGTM

If a function is marked as async, V8 will create a few extra promises
behind the scenes when awaiting it. If the function doesn't itself
contain any awaits, these extra promises just adds overhead that isn't
needed.

The `require-await` rule isn't enabled in tests, where the extra created
promises doesn't harm anyone.
@@ -10,6 +10,7 @@ addHook({ name: 'apollo-server-core', file: 'dist/runHttpQuery.js', versions: ['
const HttpQueryError = runHttpQueryModule.HttpQueryError

shimmer.wrap(runHttpQueryModule, 'runHttpQuery', function wrapRunHttpQuery (originalRunHttpQuery) {
// The original function is async, but no need to mark it as such as long as it returns a promise

Choose a reason for hiding this comment

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

Should we really add these comments? While reviewing it, I also thought about it and it seemed fine without, since the method should always return what ever it originally does.
Obviously we may not change that, but it should also never be the case, right?
I am just in favor of not having comments as they are easily outdated, forgotten and often do not help a lot.

Copy link
Collaborator

@juan-fernandez juan-fernandez Feb 19, 2025

Choose a reason for hiding this comment

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

not for this case, but for the test optimization cases, it'll help test opt devs 😄 , as it helps highlighting that async operations are indeed allowed in this wrapper function. Removing it means you either have to remember or check the test framework's (or library in general) original code

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was on the fence about this as well. The reason I ended up adding the comments in the end was because of the risk involved. If we ever decided to return something else than the return value of the original function, we needed to ensure that it was wrapped in a promise. If we didn't, something would break. So I went with the better-safe-than-sorry approach 🤷

I am just in favor of not having comments as they are easily outdated

Unless the original function is changed to not be async anymore, the comment is never outdated, right?

Copy link
Contributor

@szegedi szegedi Feb 20, 2025

Choose a reason for hiding this comment

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

I'd argue it's not necessary for the comment to elaborate on what did the function use to be before the change. "Returns a promise" feels enough to me. Or "Returns a promise, can be awaited."

If you want a bit more formalism, we can even put a JSDoc annotation on it?

/** @returns {Promise<…>} */

(I don't know what value type is the returned promise in question supposed to carry, hence ellipsis.)

Copy link
Collaborator

@juan-fernandez juan-fernandez left a comment

Choose a reason for hiding this comment

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

test optimization's perspective here.

I'm not against the change but I don't find it particularly useful: the asyncs need to be replaced by a comment or it'll make it harder to know that an async operation is possible in the wrapper. This makes legibility worse IMO.

As for the performance improvements: I understand the improvement, but is it really applying? These wrapper functions are async because the original ones are async. Example: https://github.com/jestjs/jest/blob/93ca9d3cf6db4b88393ab02b8d0007bcdf30e4bb/packages/jest-reporters/src/CoverageReporter.ts#L107

is wrapping an async function into a sync one what we want here?

This said, again, I don't oppose the change 😄

@tlhunter
Copy link
Member

Actually, can we instead have a rule to block async/await entirely? Promises too. We've had a non-written rule about never using promises in the codebase since day 1 because it can slow down async_hooks significantly.

async/await is totally fine in tests

@rochdev
Copy link
Member

rochdev commented Feb 19, 2025

async/await is totally fine in tests

Absolutely, by codebase I meant the source code, not the tests, and even that has exceptions today (for example I believe at least profiling has been using promises for a while) but the main reason it was blocked in most places is that historically it would cause issues with async_hooks. Since we instrument promises, creating promises means we're paying double the cost (actually more because of GC). As pointed out though, it's technically out of scope of the PR in the sense that I was asking for something else, but I think it would make sense to make sure through linting that we're not using promises at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants