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

Per-instance wrap #25

Open
Qard opened this issue Feb 2, 2023 · 6 comments
Open

Per-instance wrap #25

Qard opened this issue Feb 2, 2023 · 6 comments

Comments

@Qard
Copy link

Qard commented Feb 2, 2023

One of the challenges Node.js has faced with AsyncLocalStorage and the underlying async_hooks is that consumers of those APIs don't always agree on which path a given propagation should follow. For example, there's debate if it's correct to follow the path from where a promise is resolved or the path from where a then handler was attached. This path can be adjusted with AsyncContext.wrap however it currently only exists as a static impacting all stores. I would suggest also having a per-instance wrap method so any consumers that need to follow a different path from the default can graft the context graph only for their store and not break expectations of other stores by using the static version.

@legendecas
Copy link
Member

FWIW, per-instance wrap can be built on top of existing methods as simple as:

function wrap(asyncContext, fn) {
  const value = asyncContext.get();
  return function(...args) {
    asyncContext.run(value, () => fn(...args));
  }
}

Would you mind sharing an example of how this would be helpful in practice?

@Qard
Copy link
Author

Qard commented Apr 4, 2023

The use case is being able to alter the graph shape for your own representation without altering it for everyone. A good example is when tracing the fs module. If you wrap a span around every fs function and then a fs.readFile(...) happens you'll see internally that it also does fs.open(...), fs.read(...), and fs.close(...). The internal behaviour might not be relevant so many APM products will choose to step over those internals by currently using AsyncResource to link the fs.readFile(...) callback to the point it was called, however this will alter the graph globally which can break other things. Often times APM products will break each other when used together because of destructively altering the global graph. If we could alter only the graph for our own store in a fairly standard way it would be a huge benefit to the stability of APM products.

I've actually introduced something alongside the Node.js TracingChannel feature recently to somewhat produce this behaviour except specifically bound to a diagnostics_channel, which can be seen in https://github.com/nodejs/node/blob/c23e772ccc19618493937fd2a6a7addf83267ade/doc/api/diagnostics_channel.md#channelbindstorestore-transform and https://github.com/nodejs/node/blob/c23e772ccc19618493937fd2a6a7addf83267ade/doc/api/diagnostics_channel.md#channelrunstorescontext-fn-thisarg-args. It does the same selective binding of a context, but delegated through diagnostics_channel. It would be ideal if this capable was supported directly in AsyncContext though as the code to produce this behaviour externally is somewhat more complicated and confusing than it actually should need to be.

@littledan
Copy link
Member

We've added AsyncContext.Snapshot.wrap, so I think we can consider this issue resolved.

@Qard
Copy link
Author

Qard commented Apr 8, 2024

Snapshot is a container for the value of all stores though, so wouldn't that capture and restore the value of all stores? The purpose of this issue is to request the ability to do this for individual stores separately. In many cases we will want everyone to follow the same path, connection pools for example, but in some cases the correct path to follow depends on the use case and this is why a feel we need interfaces to control the flow per-store as needed.

@littledan
Copy link
Member

littledan commented Apr 8, 2024

Oh, right. Well, this would be quite a small amount of JS to implement on top of the APIs we have already, as explained above #25 (comment) , so I'm not really sure if we need it built-in. What more is needed?

@Qard
Copy link
Author

Qard commented Apr 11, 2024

It's a small amount of code on top of what is already there, but it's also I think a common enough need that making the DX of this common case just a little bit friendlier is worth the minimal additional code.

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

No branches or pull requests

3 participants