-
Notifications
You must be signed in to change notification settings - Fork 30.5k
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
Conversation
Review requested:
|
lib/diagnostics_channel.js
Outdated
@@ -136,10 +139,92 @@ function hasSubscribers(name) { | |||
return channel.hasSubscribers; | |||
} | |||
|
|||
function bindStore(name, store, build = (v) => v) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
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. 😅 |
b2b69ef
to
1a233aa
Compare
@Qard Is it intended that you created the branch here and in #44943 on nodejs/node instead on your private fork? |
lib/_http_server.js
Outdated
@@ -1009,6 +1010,15 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { | |||
}); | |||
} | |||
|
|||
if (storageChannel.hasSubscribers) { | |||
storageChannel._enter({ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
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). |
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. |
3b1d61c
to
b601ed5
Compare
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.
b601ed5
to
c6d01f6
Compare
@@ -90,6 +90,7 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => { | |||
}); | |||
|
|||
const dc = require('diagnostics_channel'); | |||
const storageChannel = dc.storageChannel('http.server'); |
There was a problem hiding this comment.
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); | ||
}); |
There was a problem hiding this comment.
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?
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 |
Yes, this could be built on top of the 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. |
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
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. |
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. |
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. |
I've created #45277 as an alternate, hopefully improved implementation of this concept. Please have a look! |
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