You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Module Hooks cannot be registered from worker thread in 22.2.0+ #53182
How often does it reproduce? Is there a required condition?
Always.
What is the expected behavior? Why is that the expected behavior?
The worker thread should start with the hooks register successfully.
What do you see instead?
The process hangs.
Removing the register call will solve the problem.
Additional information
This problem happens due to #52706, as already triaged in #53097.
An additional problem is that the suggested workaround forces people to use node --import register.js in order to solve the problem as the module thread is shared with Workers.
While this is fine, there might be situation in which users cannot use --import (or --require).
In those situation I have observed the register call will successfully work for subsequent dynamic import() call but NOT for require() (unless I missed something obvious).
Hi @ShogunPanda,
the example tries to call module.register from the worker thread, which is currently not supported as pointed out by you and as mentioned in the other issue. Why not calling the module.register from the main thread and the hooks defined in hooks.js would be active also for any worker threads that will be spawned. From my perspective that was the intended behaviour of #52706. For supporting module.register from a worker thread we decided in that discussion to postpone to a followup and the documentation PR is meant just as a temporary clarification until that is solved.
changed the title [-]Module Hooks cannot be registered from main thread in 22.2.0+[/-][+]Module Hooks cannot be registered from worker thread in 22.2.0+[/+]on May 28, 2024
Calling register from new WorkerexecArgv--import throws an error, and we document that this is expected.
Calling register from new WorkerexecArgv--import or from a worker thread behaves the same as it would if called from the main thread: a hooks thread is created if it doesn’t already exist, and the new hooks are added. The hooks apply to all threads.
I think the second solution is much preferable to the first, unless we can’t achieve it for some reason. The first could be a stopgap temporary solution, better than a hang and arguably better than the incorrect behavior before #52706, but it still kind of sucks as I think we would agree that it’s not our intended final design. Ideally we can just go straight to the second solution and skip over any stopgaps like the exception or a revert.
@dygabo and @ShogunPanda, how feasible is the second solution? Is this something that we could potentially get a PR for in the next few days?
The bug is a bit more nuanced: the hang only happens when calling register from within an execArgv--import call. Taking the example from the top post, if you change index.js to:
The process no longer hangs. The register call is a noop, but it’s not a hang. (It’s a noop in 22.1.0 too.) It’s only when the register call happens within a module referenced via --import in the new WorkerexecArgv option do we get the hang. This probably explains why our tests didn’t catch this, as execArgv: ["--import", "./lib/register.js"] is not a pattern we encourage or document. I opened https://github.com/ShogunPanda/hooks-repro/pull/1/files to update the repro with more examples.
This doesn’t really change the analysis of my previous comment; ideally register will work identically whether called from the main thread, a worker thread, or new WorkerexecArgv. This just highlights the need for tests for each scenario.
this needs attention and probably also some time to get the edge cases right. I cannot promise a few days so if this is important, please consider the revert. I can look into it (going for solution no 2.).
Please consider that even if 2. is preferable, and it would support module.register calls from the worker threads even when the main thread does not use hooks, it still represents a change in behaviour compared to the state before #52706. This whole change cannot be expected to have a different implementation but the same effect like the previous version. Having one hook registered by thread A running for imports on thread B (and the other way around) and for those on the main thread for subsequent imports requires that the hooks are crafted with that restriction in mind. I just want to make sure that if we invest time and energy in a solution, it will be a solution that is accepted and one that we can work with (stable). Allowing module.register at arbitrary moments in time in the code could be also problematic because we would have scenarios where module A on the main thread was loaded by using one set of customization hooks while module B (being imported later also on main thread) might be loaded using another set of hooks because there is one additional thread that decided to register one more hook. These scenarios would end up being difficult to debug and to reason about. Maybe having a more restrictive interface would make the feature easier to adopt and use.
Please consider that even if 2. is preferable, and it would support module.register calls from the worker threads even when the main thread does not use hooks, it still represents a change in behaviour compared to the state before #52706.
I think the intended behavior, where there’s only ever one hooks thread and it applies to all application threads, is the base we need to ship. That’s the use case of “I’m writing an application in TypeScript, and my app has worker threads. I want to be able to write my entire app in TypeScript, including the worker threads, with minimal overhead.” Forcing one hooks thread per application thread all the time is a performance hit for most users. We could permit that as an alternative mode that users can enable somehow, but that’s a feature request that we can discuss once we get the intended behavior working.
@ShogunPanda I think there are (at least) two issues here:
From 22.2.0, calling register from new WorkerexecArgv--import causes a process hang.
From a long time back, calling register from worker code is a noop.
I think perhaps this issue should focus on the first one, and a new issue could be opened for the latter? Since I assume you’re mostly concerned about the hang introduced in 22.2.0.
Quick update from our recent team meeting: We invited Gil Tayar (author/maintainer of several pertinent libraries) to discuss spawning a dedicated loaders thread per user-land thread, and he noted that will add enormous complexity to library authors (on top of the extra complexity on node’s side). We decided for an initial implementation, it would be better to use a single loaders thread shared by all user-land threads and add caveats to the relevant sections of the docs. If there is sufficient appetite, we can subsequently add a configuration option (perhaps to the Worker constructor) to spawn dedicated loaders threads.
However, the module hooks appear to always use the conditions of the main thread. e.g. when starting a script with conditions that spawns a worker with different conditions:
Activity
ShogunPanda commentedon May 28, 2024
CC: @mcollina @dygabo @GeoffreyBooth
dygabo commentedon May 28, 2024
Hi @ShogunPanda,
the example tries to call
module.registerfrom the worker thread, which is currently not supported as pointed out by you and as mentioned in the other issue. Why not calling themodule.registerfrom the main thread and the hooks defined inhooks.jswould be active also for any worker threads that will be spawned. From my perspective that was the intended behaviour of #52706. For supportingmodule.registerfrom a worker thread we decided in that discussion to postpone to a followup and the documentation PR is meant just as a temporary clarification until that is solved.index.js:and then
node index.jsshould work, the hooks inhooks.jsare imported and they are active also on the instantiated Worker.Would that work in your case?
targos commentedon May 28, 2024
The title of this issue seems wrong, if it's about registering from a worker thread.
[-]Module Hooks cannot be registered from main thread in 22.2.0+[/-][+]Module Hooks cannot be registered from worker thread in 22.2.0+[/+]ShogunPanda commentedon May 28, 2024
@targos It was a mental typo. Fixed. Thanks.
mcollina commentedon May 28, 2024
@dygabo none of the limitations introduced by #52706 were listed in the OP.
ShogunPanda commentedon May 28, 2024
@dygabo I can see that, but the problem is that the process hangs rather than immediately throwing an exception.
GeoffreyBooth commentedon May 28, 2024
@nodejs/loaders
GeoffreyBooth commentedon May 29, 2024
So there are two solutions that I can think of:
Calling
registerfromnew WorkerexecArgv--importthrows an error, and we document that this is expected.Calling
registerfromnew WorkerexecArgv--importor from a worker thread behaves the same as it would if called from the main thread: a hooks thread is created if it doesn’t already exist, and the new hooks are added. The hooks apply to all threads.I think the second solution is much preferable to the first, unless we can’t achieve it for some reason. The first could be a stopgap temporary solution, better than a hang and arguably better than the incorrect behavior before #52706, but it still kind of sucks as I think we would agree that it’s not our intended final design. Ideally we can just go straight to the second solution and skip over any stopgaps like the exception or a revert.
@dygabo and @ShogunPanda, how feasible is the second solution? Is this something that we could potentially get a PR for in the next few days?
GeoffreyBooth commentedon May 29, 2024
The bug is a bit more nuanced: the hang only happens when calling
registerfrom within anexecArgv--importcall. Taking the example from the top post, if you changeindex.jsto:And then change
lib/child.jsto:The process no longer hangs. The
registercall is a noop, but it’s not a hang. (It’s a noop in 22.1.0 too.) It’s only when theregistercall happens within a module referenced via--importin thenew WorkerexecArgvoption do we get the hang. This probably explains why our tests didn’t catch this, asexecArgv: ["--import", "./lib/register.js"]is not a pattern we encourage or document. I opened https://github.com/ShogunPanda/hooks-repro/pull/1/files to update the repro with more examples.This doesn’t really change the analysis of my previous comment; ideally
registerwill work identically whether called from the main thread, a worker thread, ornew WorkerexecArgv. This just highlights the need for tests for each scenario.dygabo commentedon May 29, 2024
this needs attention and probably also some time to get the edge cases right. I cannot promise a few days so if this is important, please consider the revert. I can look into it (going for solution no 2.).
Please consider that even if 2. is preferable, and it would support
module.registercalls from the worker threads even when the main thread does not use hooks, it still represents a change in behaviour compared to the state before #52706. This whole change cannot be expected to have a different implementation but the same effect like the previous version. Having one hook registered bythread Arunning for imports onthread B(and the other way around) and for those on the main thread for subsequent imports requires that the hooks are crafted with that restriction in mind. I just want to make sure that if we invest time and energy in a solution, it will be a solution that is accepted and one that we can work with (stable). Allowingmodule.registerat arbitrary moments in time in the code could be also problematic because we would have scenarios wheremodule Aon the main thread was loaded by using one set of customization hooks whilemodule B(being imported later also on main thread) might be loaded using another set of hooks because there is one additional thread that decided to register one more hook. These scenarios would end up being difficult to debug and to reason about. Maybe having a more restrictive interface would make the feature easier to adopt and use.GeoffreyBooth commentedon May 29, 2024
I think the intended behavior, where there’s only ever one hooks thread and it applies to all application threads, is the base we need to ship. That’s the use case of “I’m writing an application in TypeScript, and my app has worker threads. I want to be able to write my entire app in TypeScript, including the worker threads, with minimal overhead.” Forcing one hooks thread per application thread all the time is a performance hit for most users. We could permit that as an alternative mode that users can enable somehow, but that’s a feature request that we can discuss once we get the intended behavior working.
@ShogunPanda I think there are (at least) two issues here:
registerfromnew WorkerexecArgv--importcauses a process hang.registerfrom worker code is a noop.I think perhaps this issue should focus on the first one, and a new issue could be opened for the latter? Since I assume you’re mostly concerned about the hang introduced in 22.2.0.
ShogunPanda commentedon May 29, 2024
@GeoffreyBooth Agreed: if we solve the hang we might not need the revert so quickly. But I don't really see the no-op as a long time solution
GeoffreyBooth commentedon May 29, 2024
I just meant that the noop is a separate bug, since it predates 22.2.0. But maybe we can solve both together.
ShogunPanda commentedon May 29, 2024
Oh, I see! Yes, I hope we can fix it both at the same time.
Flarna commentedon May 29, 2024
To make it easier for people to get into the history of this:
PR which introduced the loader thread: #44710
corresponding issue where quite some discussions happend: #43658
FWIW the question one thread for all or one per worker was already there: #43658 (comment) and that time it was one per thread (see here).
Similar in the PR (e.g. here).
Is there somewhere a condensed decission finding document from that time?
GeoffreyBooth commentedon May 29, 2024
See https://github.com/nodejs/loaders#milestone-1-parity-with-commonjs
GeoffreyBooth commentedon May 30, 2024
See the very next comment: #43658 (comment)
Meeting notes: nodejs/loaders#118 (comment)
daniel-nagy commentedon May 30, 2024
I don't know if this is the appropriate place to comment but I think I discovered another issue with the common hooks thread and workers.
In a worker it is possible to override the import conditions of the parent thread. e.g.
However, the module hooks appear to always use the conditions of the main thread. e.g. when starting a script with conditions that spawns a worker with different conditions:
The worker conditions will be ignored by the module hooks and the conditions of the main thread will be applied.
GeoffreyBooth commentedon May 31, 2024
#50885
daniel-nagy commentedon May 31, 2024
@GeoffreyBooth Thanks for linking that issue. I was loosing my mind 😅
GeoffreyBooth commentedon Jun 18, 2024
Should be fixed by #53183, please reopen if not.