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

async_hooks Use Case #46232

Open
anywhichway opened this issue Jan 17, 2023 · 9 comments
Open

async_hooks Use Case #46232

anywhichway opened this issue Jan 17, 2023 · 9 comments
Labels
async_hooks Issues and PRs related to the async hooks subsystem. doc Issues and PRs related to the documentations.

Comments

@anywhichway
Copy link

anywhichway commented Jan 17, 2023

Affected URL(s)

https://nodejs.org/api/async_hooks.html#async-hooks

Description of the problem

I am not sure how I would accomplish the tracking of un-resolved async functions in this testing tool https://github.com/anywhichway/benchtest, without async_hooks in any kind of simple manner. The Node docs request the submission of a use case for async_hooks since the API is not recommended.

Currently the tool only requires a few lines of code:

const asyncTracker = new Map(),
    asyncHook = async_hooks.createHook({
        init: (asyncId, type, triggerAsyncId, resource) => {
            asyncTracker.set(asyncId,{type,triggerAsyncId,resource})
        },
        destroy: asyncId => {
            asyncTracker.delete(asyncId);
        },
        promiseResolve: asyncId => {
            asyncTracker.delete(asyncId);
        },
    }),
    trackAsync = (on) => {
        if(on) {
            if(!asyncTracker.enabled) {
                asyncTracker.clear();
                asyncHook.enable();
            }
        } else if(!on && asyncTracker.enabled){
            asyncHook.disable();
        }
        asyncTracker.enabled = on;
    };
@anywhichway anywhichway added the doc Issues and PRs related to the documentations. label Jan 17, 2023
@aduh95
Copy link
Contributor

aduh95 commented Jan 17, 2023

@nodejs/async_hooks @nodejs/tsc

@aduh95 aduh95 added the async_hooks Issues and PRs related to the async hooks subsystem. label Jan 17, 2023
@GeoffreyBooth
Copy link
Member

@anywhichway Can you describe the use case in terms of what you’re aiming to achieve? From the readme of the tool it sounds like you’re trying to measure how many async resources are created as part of running tests? What is the purpose of that measurement?

@Qard Is the above achievable via AsyncLocalStorage or some other API?

@Qard
Copy link
Member

Qard commented Jan 17, 2023

What do you need the resource for? That's the main thing we're trying to avoid exposing as it's an internal handle. Is type and id sufficient for your use case?

@anywhichway
Copy link
Author

anywhichway commented Jan 17, 2023 via email

@anywhichway
Copy link
Author

anywhichway commented Jan 17, 2023 via email

@Qard
Copy link
Member

Qard commented Jan 17, 2023

Try not to think in terms of how the existing API works. Our goal for gathering feedback is to identify specific use cases and how we can serve them more directly, with better performance and usability characteristics due to not needing to adapt some lower-level firehose of data to the scope you actually want.

What specific insights are you trying to gather from the runtime? You want to measure between two points, before and after a test, correct? And between those two points you want to know which async tasks are new and incomplete? Do you care about tasks which completed before the window closed? What about tasks that started before the window opened? Would you want unresolved promises to be expressed differently from libuv handles/requests consuming actual hardware resources? Would you want configurability to gather them separately only as-needed?

One of the several usability issues I have found with async_hooks is that it expresses libuv async tasks together with promises and other non-resource-constrained async like individual timers or AsyncResource to wrap userland asynchronous execution reorganization through things like connection pooling mechanisms or really anything that can store a function to be run by some later trigger abstractly from the underlying asynchrony of the runtime. I've been wanting to break async_hooks down into interfaces to monitor specifically socket and file descriptors which are a consumable resource from the expression of any sort of unresolved task.

There's also currently a significant continuous cost to async_hooks due to it needing to run its logic synchronously at every async barrier whereas the actual insight to be gathered from it typically doesn't actually matter until some periodic points, making continuous data management unnecessarily expensive.

Does that give you any ideas on how you'd like to see an interface for this expressed? I really want to focus on usability and performance here so we can have something that serves the need without the currently significant cost.

@anywhichway
Copy link
Author

anywhichway commented Jan 18, 2023

@Qard thanks for the detailed response.

Note, although I understand why AsyncFunction can't be monkey patched, none of this would be necessary if it could or if the ECMA spec dictated that asyncs use the global Promise object internally so that its monkey patch could apply. This is not to say I am asking for this. It is simply a route to my goal, albeit one possibly fraught with risk.

Here are my requirements:

  1. Know how many async requests occurred between point in time A and point time B
  2. Know how many of the async requests that occurred between A and B have resolved or rejected
  3. Computing the counts from other data returned by the tracker would be fine
  4. It should be possible to turn the tracker on and off. This ensures that the tracking process will only impact the performance of the period in which it is running and establishes time A and time B.
  5. Ideally the tracking process could report how much heap it is using for its own purposes.

For examples of APIs approaches that might work see cpuUsage and memoryUsage. An alternative would be something like:

const tracker = createResourceTracker({type:"asyncs",measure:["created","resolved","rejected"]},...otherThingsToTrack);
tracker.start();
// ... any code ....
tracker.stop();
const report = tracker.report(); // an object with each property being a tracked type
const {asyncs} = report; // an object
const {created, resolve, rejected} = asyncs; // all numbers
const {heapUsed,external}= tracker.memoryUsage(); // the memory used by the tracker NOT the code between A and B

tracker.report() could alternatively return an array of objects like

{type:"async",{measurements:{create: 10,resolved:5,rejected:0}}

Note otherThingsToReport could be memory, cpu, sockets, timeouts, etc., e.g.

{type:"memory",metrics:["rss","heapUsed"]}

Finally, created, resolved, and rejected could be arrays of Promises and the caller could get lengths or even diff the arrays for detail and await promises that have not resolved and time the resolution.

If you like the generic approach above cpuUsage and memoryUsage could be used behind the scenes initially; however, I don't think getActiveResources provides sufficient detail.

I hope the above is useful and addresses you questions/directions. Happy to respond further.

Thanks to all on the team for the astonishingly quick responses to date!

@legendecas
Copy link
Member

legendecas commented Jan 31, 2023

Those statistics counter sounds similar to the PerformanceResourceTiming (which can be produced by the fetch API). The entries are available through the PerformanceObserver, and it generates no performance impacts if no observer is present and disabled from timeline.

@anywhichway
Copy link
Author

anywhichway commented Jan 31, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
async_hooks Issues and PRs related to the async hooks subsystem. doc Issues and PRs related to the documentations.
Projects
None yet
Development

No branches or pull requests

5 participants