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

diagnostics_channel: add storage channel #44894

Closed
wants to merge 1 commit into from

Conversation

Qard
Copy link
Member

@Qard Qard commented Oct 4, 2022

A storage channel integrates between diagnostics_channel and AsyncLocalStorage for the publisher to define a scope in which a store should run and what data to provide in the scope when running.

I'll write some proper docs for this after some discussion first to verify the current design satisfies those of us that need this.

cc @nodejs/diagnostics @jasnell

@Qard Qard added async_hooks Issues and PRs related to the async hooks subsystem. diagnostics_channel Issues and PRs related to diagnostics channel labels Oct 4, 2022
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Oct 4, 2022
lib/_http_server.js Outdated Show resolved Hide resolved
@@ -136,10 +139,92 @@ function hasSubscribers(name) {
return channel.hasSubscribers;
}

function bindStore(name, store, build = (v) => v) {
Copy link
Member

Choose a reason for hiding this comment

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

diagnosticChannel.bindStore reads to me that the diagnostic channel can only be bound to one single AsyncLocalStorage at one time. I believe this is not the intention, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the intent is the reverse, that the store is being bound to the storage channel.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

I don't understand why it's called the storage channel.

lib/_http_server.js Outdated Show resolved Hide resolved
lib/diagnostics_channel.js Outdated Show resolved Hide resolved
@Qard
Copy link
Member Author

Qard commented Oct 6, 2022

It's called StorageChannel mainly because it uses two channels as delimiters marking where a storage context should be run. I'm open to suggestions for better names though. Still doing a bunch of iterating though so could change completely. 😅

@Flarna
Copy link
Member

Flarna commented Oct 12, 2022

@Qard Is it intended that you created the branch here and in #44943 on nodejs/node instead on your private fork?

doc/api/diagnostics_channel.md Show resolved Hide resolved
lib/_http_server.js Outdated Show resolved Hide resolved
lib/diagnostics_channel.js Outdated Show resolved Hide resolved
@@ -1009,6 +1010,15 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
});
}

if (storageChannel.hasSubscribers) {
storageChannel._enter({
Copy link
Member

Choose a reason for hiding this comment

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

if there is the need for the undocumented _enter/_exit here it's likely that they are also useful at other places outside of core.
Is there a reason to keep them internal?

Copy link
Member Author

Choose a reason for hiding this comment

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

I want to avoid exposing those if at all possible because it's easy to miss one side of it and screw things up. Best to wait and see if a specific use-case comes up and consider exposing it then. I'm not convinced we actually need it though.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe change the usage in http to use run?

Copy link
Member Author

Choose a reason for hiding this comment

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

That adds an extra closure though, which I'd prefer to avoid when we can. The intent is for internals to use the lower-level interface directly rather than creating closures. It's more error-prone, but core is a surface area of code we control so we can easily ensure it's used correctly. Exposed to the ecosystem though we have less ability to guide away from using it wrong.

doc/api/diagnostics_channel.md Outdated Show resolved Hide resolved
lib/diagnostics_channel.js Outdated Show resolved Hide resolved
@Flarna
Copy link
Member

Flarna commented Oct 12, 2022

It took me a while to fully understand what this does and is used for. So I think docs need some more work and maybe a better sample. But as usual in this ALS topics that's not easy.

I'm also not sure if this is really needed. If e.g. http issues enter/exit events via existing channels the same can be reached by calling enter/exit on your store in your callback. StorageChannel seems to be quite specific for a single use case to get something on ALS. Is it intended to add more such classes/APIs if similar use cases pop up?

The actual requirement here for DC is that the lib in question (http here) is reliable calling enter/exit (or start/end).

@Qard
Copy link
Member Author

Qard commented Oct 12, 2022

Nope. Not intended. Brand new computer and forgot to set up my new Node.js checkout properly. 😅

The purpose of a custom API for this is to have a standard interface that other environments can implement to handle the request wrapping concept in a standard way. Some FaaS environments want this.

@Qard Qard force-pushed the diagnostics-channel-storage branch 2 times, most recently from 3b1d61c to b601ed5 Compare October 18, 2022 00:06
@Qard Qard marked this pull request as ready for review October 18, 2022 03:02
@Qard Qard added the request-ci Add this label to start a Jenkins CI on a PR. label Oct 18, 2022
A storage channel integrates between diagnostics_channel and
AsyncLocalStorage for the publisher to define a scope in which a
store should run and what data to provide in the scope when running.
@Qard Qard force-pushed the diagnostics-channel-storage branch from b601ed5 to c6d01f6 Compare October 18, 2022 03:12
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Oct 18, 2022
@nodejs-github-bot
Copy link
Collaborator

@@ -90,6 +90,7 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
});

const dc = require('diagnostics_channel');
const storageChannel = dc.storageChannel('http.server');
Copy link
Member

Choose a reason for hiding this comment

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

Can you document somewhere that there is an http.server channel?

assert.strictEqual(store.getStore(), undefined);
get(`http://localhost:${port}`, (res) => res.resume());
assert.strictEqual(store.getStore(), undefined);
});
Copy link
Member

Choose a reason for hiding this comment

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

Can you expand this test a bit more with an actual use case for this API?

@Flarna
Copy link
Member

Flarna commented Oct 18, 2022

It's called StorageChannel mainly because it uses two channels as delimiters marking where a storage context should be run.

Aren't this exactly the same delimiters as for the sync part of the tracing channel proposed in #44943?

I think it's hard for a library developer to choose between StorageChannel, Channel and Tracing Channel.

@Qard
Copy link
Member Author

Qard commented Oct 18, 2022

Yes, this could be built on top of the start and end events of TracingChannel. We do need a version of that explicitly for binding storage scopes though. The general interface of this can remain the same while being rebuilt on top of TracingChannel later though, so I think it's not important that one lands before the other.

I was thinking though that it might be worth inverting the relation here to instead put an interface on AsyncLocalStorage to bind an instance to a TracingChannel. I feel like that might make the relationship clearer. I think I will start another PR for that and see how that goes. I'll link that here when it's ready.

@Flarna
Copy link
Member

Flarna commented Oct 19, 2022

We do need a version of that explicitly for binding storage scopes though

Why is this needed inside core/dc? In think the actual need is to get consistent, synchronous start/end notification. The actual enter/exit call to AsyncLocalStore (or whatever else is needed) can happen inside the user callback. Sure, having it in core makes it easier but effective the binding to AsyncLocalStore is just one specific use case. There are other topics usually happening in these notifications like:

  • collect wall clock time (which one is up to user, e.g. Date.now(), performance.now(), something synced with backend,...)
  • collect CPU times
  • instrument/bind EventEmitters and/or install listeners
  • collect data describing the operation
  • extract/inject data needed for linking operations (e.g. W3C tracecontext)
  • ...

A library author can't (and should not) know what users do with the data provided. They can just give notification that some operation started/ended along with the metadata.
The usecase to extract/inject something like W3C tracecontext headers is already a bit problematic once you consider that there might be more subscribers and all of them tinker with the same underlying single data set.

@Qard
Copy link
Member Author

Qard commented Oct 20, 2022

It needs to be in core because there is no way to do this without enterWith which is unsafe and thus we would like to discourage use. We intend for AsyncLocalStorage to exist in runtimes other than Node.js though, so it needs a consistent and safe solution, which is what this feature is meant to solve.

@Flarna
Copy link
Member

Flarna commented Oct 20, 2022

I doubt that we can get rid of enter/exit in AsyncLocalStorage - except by adding something similar on diagnostics channel which makes it just more complicated as it adds an indirection. Currently enter/exit on StorageChannel are internal but above you mentioned that it may be needed to expose them.

OpenTelemetry has currently no enter/exit in their context manager API but there are request for it since quite a while.
Looking into e.g. .NET which has AsyncLocal longer then node - it allows to set a value which is mostly the same as our enter.

@Qard
Copy link
Member Author

Qard commented Nov 2, 2022

I've created #45277 as an alternate, hopefully improved implementation of this concept. Please have a look!

@Qard Qard closed this Nov 18, 2022
@Flarna Flarna deleted the diagnostics-channel-storage branch October 30, 2023 12:08
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. diagnostics_channel Issues and PRs related to diagnostics channel http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants