nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
105.69k stars 28.68k forks source link

async_hooks Use Case #46232

Open anywhichway opened 1 year ago

anywhichway commented 1 year ago

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;
    };
aduh95 commented 1 year ago

@nodejs/async_hooks @nodejs/tsc

GeoffreyBooth commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

Sure ... the purpose is to identify how many ansyc resources have not yet resolved when unit tests complete. As you may note in the code snippet, the asyncId is removed from the Map when it fails or resolves. However, I may provide an option to simply track how many asyncs are created.

Failure to resolve async resources may be indicative of missing error handling, sequencing issues in code execution, failure of other components under stress, failure to execute additional functionality because "thens" will never process, memory than can't be garbage collected or manually freed (e.g. clearing a Map) unless the promise resolves. Unexpectedly high volumes of ansyc resources may be indicative of runaway code or the need to inject debounce functionality.

Make sense?

Sent from my T-Mobile 5G Device Get Outlook for Androidhttps://aka.ms/AAb9ysg


From: Geoffrey Booth @.> Sent: Tuesday, January 17, 2023, 11:02 AM To: nodejs/node @.> Cc: Simon Y. Blackwell @.>; Mention @.> Subject: Re: [nodejs/node] async_hooks Use Case (Issue #46232)

@anywhichwayhttps://github.com/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?

@Qardhttps://github.com/Qard Is the above achievable via AsyncLocalStorage or some other API?

— Reply to this email directly, view it on GitHubhttps://github.com/nodejs/node/issues/46232#issuecomment-1385740738, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ABF2USZPTRCAKX5MWYJQMW3WS3GBBANCNFSM6AAAAAAT52PKCI. You are receiving this because you were mentioned.Message ID: @.***>

anywhichway commented 1 year ago

Yes, type and id would be adequate.

The current Node process function getActiveResources is poorly documented but does not appear to capture pending Promise or async resolution. If it did, that would be adequate for my current use case. Although, it would still fail to capture how many asyncs get generated overall (see other comments on this issue).

Sent from my T-Mobile 5G Device Get Outlook for Androidhttps://aka.ms/AAb9ysg


From: Stephen Belanger @.> Sent: Tuesday, January 17, 2023 11:26:50 AM To: nodejs/node @.> Cc: Simon Y. Blackwell @.>; Mention @.> Subject: Re: [nodejs/node] async_hooks Use Case (Issue #46232)

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?

— Reply to this email directly, view it on GitHubhttps://github.com/nodejs/node/issues/46232#issuecomment-1385774433, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ABF2US73PFCVQOLJMPNETNDWS3I5VANCNFSM6AAAAAAT52PKCI. You are receiving this because you were mentioned.Message ID: @.***>

Qard commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

I would be fine with API style to collect the other data for which I am looking.

Sent from my T-Mobile 5G Device Get Outlook for Androidhttps://aka.ms/AAb9ysg


From: Chengzhong Wu @.> Sent: Tuesday, January 31, 2023 1:20:12 AM To: nodejs/node @.> Cc: Simon Y. Blackwell @.>; Mention @.> Subject: Re: [nodejs/node] async_hooks Use Case (Issue #46232)

Those statistics counter sounds similar to the PerformanceResourceTiminghttps://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming (which can be produced by the fetch API), which is available through the PerformanceObserverhttps://nodejs.org/dist/latest-v18.x/docs/api/perf_hooks.html#class-perf_hooksperformanceobserver.

— Reply to this email directly, view it on GitHubhttps://github.com/nodejs/node/issues/46232#issuecomment-1409883571, or unsubscribehttps://github.com/notifications/unsubscribe-auth/ABF2US2AOBKPVLBHNF3SDEDWVC4KZANCNFSM6AAAAAAT52PKCI. You are receiving this because you were mentioned.Message ID: @.***>