-
Notifications
You must be signed in to change notification settings - Fork 16
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
Maintaining hook module registration for on-thread hooks #203
Comments
To elaborate the current // register.js
module.register('/path/to/hook.js'); // inherited by all child workers by default
// hook.js
export function resolve() { ... } Where as I am proposing in #198 to switch to take the functions directly in the API, with a separate API to configure the inheritance: // This tells Node.js that from now on all the child worker/process
// spawned from the current instance should preload this file.
process.preloadInChildren(__filename); // or import.meta.filename
module.registerHooks({
resolve() { ... }
});
This can also be used in other use cases that are not related to module hooks (e.g. APM hooks that only cares about generic stats like v8 heap statistics/event loop utilization in all children instances, or not patching modules to do tracing when it's turned off). We can also add APIs to allow more control for the preload scripts (querying, disabling). |
cc @nodejs/loaders plus the participants of #103: @cspotcode @giltayar @arcanis @bumblehead @bizob2828 plus participants of nodejs/node#53332: @VoltrexKeyva @Flarna @alan-agius4 |
I think we need granular control on how the loaders are passed from parent to children. Moreover, we need to consider the case of a child process/thread changing the hooks too. There are a few of edge cases to consider, and how a developer could "reset the chain". |
I agree with @mcollina. I like the approach taken by @joyeecheung but we need to be able to diverge from it, even during runtime. |
Do you mean:
I think with a flexible API it can be done as: // Main thread
require('hooks.js')
new Worker('worker2.js', ...);
// hooks.js
process.preloadInChildren(__filename);
module.register({
...
deregister() {
process.removePreloadInChildren(__filename);
}
})
// Worker 2
// Has preloaded hooks set by main thread
new Worker('user.js', ...);
// Worker 3
// Has preloaded hooks set by main thread
for (const hook of module.registeredHooks()) {
hook.deregister();
}
// Or register new hooks with module.registeredHooks()
// Or wrap the hook manipulation code in a separate file
// and set it as preload in children if different behavior needs to be inherited |
Actually if we want to make it possible for the hooks to be dynamically enabled/disabled, we should add callbacks for users to customize what should be done when the hook is enabled/disabled. |
I'm more thinking something like: new Workers('user.js', { hooks: [] }) or similar. |
@mcollina something like that would be great to see. I think there are two kinds of use cases we have here:
My main concern here is in the case of (2) where it shouldn't be a requirement that every worker instantiation must be updated to set the hooks, instead having the default of compartment sharing for workers will significantly improve APM ergonomics. So my preference would be for allowing customization, but with a good default experience. |
I would have separate APIs for explicit hook passing and implicit sharing. Explicit: new Workers('user.js', { hooks: [] }) Implicit: Worker.setDefaultHooks([]) I might even go as far as only allowing implicit hook inheritance via a cli flag. In my opinion it's a bit of an anti-pattern applying implicit changes to sub-environments without explicit action from the parent. While APM would like to inject itself into every thread, it's a bit of a security concern if child threads inherit code which was not explicitly shared with it and therefore perhaps not intended to be shared. |
In principle I'm all for a My concern is that the existing status quo of the register API solves for hook portability, in a way that is compatible with That is, I simply want us to maintain the existing design of a registered hooks module. I could be okay with not making hook registration for workers the default, but the important point is that changing that default should be easy. For example one could imagine the register API supporting a custom option for this: module.registerHooks(import.meta.resolve('./hooks/mjs'), { childWorkers: true });
Worker threads are quite frankly not a security boundary. Can you elaborate on the specific security concern here? If anything security based loaders and global attenuation that acts as enforcing security properties of the environment would very much want to be setting |
If there are concerns around |
Exactly, that was the scenario I was thinking about.
I love that! |
I don't think we should rely on a single user to decide on propagating hooks to workers or not.
I think each individual tool/library should have the possibility to set the hooks in a way it is required to operated correct. Delegating all into a single user tends to be tedious. |
To be clear - this is exactly what we have currently and I am only advocating to retain the status quo in Node.js. Each tool and library can choose how it sets hooks, there is no single hooks "owner". All I am requesting is that we either make it a default to allow hooks to work on workers, or as a compromise, allow a simple flag to enable this feature such as The constraint specifically is that hook functions are not transferrable to workers, and if workers are to have sync hooks on their own threads, we should design a worker hooks system that is by default transferrable, as opposed to by default not transferrable. The ultimate reason being one of encouraging workers, not adding more frictions to them (there are enough frictions with workers for JS, and it is our job to remove those to make the language more multi threaded and faster). |
I can imagine an application that relies on an APM tool:
Something like: require('cool-apm')
console.log('app started')
const worker = new Worker('./some.js')
// wait for worker to do things I know that the tool I work on promotes omitting that first line and starting the app like: But we also don't really support threads. In short, users are going to expect things to just work. |
Most APMs don't really do much of anything about threads currently, or just fallback on |
I noticed that with nodejs/node#53787 a hook-specific inheritance setting can be somewhat awkward, if we want to make that config to support "preload this file for all Node.js instances, main thread or workers". The best experience overall I think would be something like users specifying a noderc entry to preload |
We discussed in the meeting this week and we agreed that we should let a dedicated preload configuration to handle this, ideally via something like nodejs/node#53787 To summarize why specifier + parentURL based inheritance doesn't work:
At the end of the day I think for auto registration of end module hooks to work it still needs automatic inheritance for arbitrary scripts that consume the low-level hook, so automatic inheritance of a low-level hook module itself is just redundant. |
One of the benefits of having a
module.register
hook module registration like we currently do for loaders in that it applies for all spawned workers is that by default, resolver, network and transpilation loaders automatically can be applied to all spawned workers.It is being discussed to migrate to same-thread hooks as an option of the discussion in #201, while possibly also unifying with the synchronous hooks proposal in #198.
Currently the synchronous hooks proposal does not use
register
to register a hooks module, but instead takes arbitrary functions.I'd like to suggest we consider maintaining something similar to our existing
register
API in providing a module for hook definitions, as this more naturally allows hooks to be shared between different loading contexts in a portable way.This is separate to the question of hook customization across contexts, which could also in turn have manual configuration - for example some kind of pattern like
new Worker(path, { register: 'custom-module' })
ornew Worker(path, { hooks: 'blank' })
could be used at worker instantiation time. In addition it might even be possible to register hooks that don't get shared by default something likeregisterHooks(import.meta.resolve('./hooks.js'), { shared: false })
.Specifically, the default story of hooks using the same code implementation by default is what we get for workers today and were looking to get for a shared hooks worker. I think this would be a really valuable property to keep where the default user experience for writing loaders from transpilers to APM is that they do work across contexts, without that being a major structural change to the loader code only to be discovered too late in the loader authoring process.
The text was updated successfully, but these errors were encountered: