-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
fix(core): only start plugin workers once #22222
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Ignored Deployment
|
☁️ Nx Cloud ReportCI is running/has finished running commands for commit 4c2c2f3. As they complete they will appear below. Click to see the status, the terminal output, and the build insights. 📂 See all runs for this CI Pipeline Execution ✅ Successfully ran 6 targets
Sent with 💌 from NxCloud. |
349635f
to
aedac1b
Compare
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.
This looks good in general. I agree with returning the callbacks to shut plugins down, and then the overall callback from loadNxPlugins
would shutdown all of the plugins from that run. I think we need to be a bit more careful s.t. we arent calling shutdownPluginWorkers
multiple times when multiple plugins are in use. Imagine we have 5 plugins. Currently, this looks like:
Load Nx Plugins ->
- Load Nx Plugin * 5
- Each returned cleanup fn shutsdown all 5 workers
- Returns a cleanup fn that calls all 5 cleanup fns
cleanup() - Calls returned cleanup fn once
- Calls all 5 sub-cleanup functions
- Calls shutdownPluginWorkers which tries to kill all 5 workers
- Calls all 5 sub-cleanup functions
Its probably fine , but is a bit messy.
export async function loadNxPluginAsync( | ||
pluginConfiguration: PluginConfiguration, | ||
paths: string[], | ||
projects: Record<string, ProjectConfiguration>, | ||
root: string | ||
): Promise<LoadedNxPlugin> { | ||
const { plugin: moduleName, options } = | ||
typeof pluginConfiguration === 'object' | ||
? pluginConfiguration | ||
: { plugin: pluginConfiguration, options: undefined }; | ||
|
||
performance.mark(`Load Nx Plugin: ${moduleName} - start`); | ||
let { pluginPath, name } = await getPluginPathAndName( | ||
moduleName, | ||
paths, | ||
projects, | ||
root | ||
); | ||
const plugin = normalizeNxPlugin(await importPluginModule(pluginPath)); | ||
plugin.name ??= name; | ||
performance.mark(`Load Nx Plugin: ${moduleName} - end`); | ||
performance.measure( | ||
`Load Nx Plugin: ${moduleName}`, | ||
`Load Nx Plugin: ${moduleName} - start`, | ||
`Load Nx Plugin: ${moduleName} - end` | ||
); | ||
return { plugin, options }; | ||
} |
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'm really not a big fan of this making it over into the internal-api
file. Ideally, The plugin worker should only have to import from worker-api and utils, to ensure that there is a clean separation between the stuff happening there and the rest of the code.
Additionally, if a plugin were to set its create nodes pattern based on the result of a config file (similar to how the package-json-workspaces plugin works) we could very easily hit a situation where swc-node and ts-node are both registered on main thread. Imagine if instead of package.json having the workspaces field it was something like workspace.ts that required a transpiler.
If the only reason we are doing this is to support listing createNodes
and createDependencies
support in nx list
, I'd honestly rather drop that support and we could add some field to package.json if a plugin wants to list that they support it or similar. e.g. something like
{
"nx-plugin": {
"generators": "generators.json"
"executors": "executors.json"
"graph": ... // this could be a boolean to indicate support, but maybe a path to import would work well here. Then we would be able to drop the /plugin at the end of a lot of our PCV3 plugins 🤔 .
}
}
return [ | ||
new Promise<RemotePlugin>((res, rej) => { | ||
worker.on('message', createWorkerHandler(worker, res, rej)); | ||
worker.on('exit', createWorkerExitHandler(worker)); | ||
}), | ||
() => { | ||
shutdownPluginWorkers(); | ||
}, | ||
] as const; |
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 feel like this should be an object so we have more well defined labels (e.g.)
return {
plugin: RemotePlugin
shutdown: () => Promise<void>
}
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.
Mm.. Idk how much of a difference it makes. I'm going to keep it a tuple for the interest of time.
worker.on('exit', createWorkerExitHandler(worker)); | ||
}), | ||
() => { | ||
shutdownPluginWorkers(); |
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.
This callback should probably shut down the single plugin that was just loaded. (e.g. shutdownPluginWorker(worker))
} | ||
|
||
let pluginWorkersShutdown = false; | ||
|
||
export async function shutdownPluginWorkers() { | ||
async function shutdownPluginWorkers() { |
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 we make the changes above, this becomes shutdownPluginWorker
|
||
export async function loadNxPlugins( | ||
export async function loadNxPluginsRemotely( |
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.
export async function loadNxPluginsRemotely( | |
export async function loadNxPluginsViaWorker( |
} | ||
|
||
export async function loadNxPlugin( | ||
export function loadNxPluginRemotely( |
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.
export function loadNxPluginRemotely( | |
export function loadNxPluginViaWorker( |
try { | ||
return packageJson.generators ?? | ||
packageJson.executors ?? | ||
packageJson['nx-migrations'] ?? | ||
packageJson['schematics'] ?? | ||
packageJson['builders'] | ||
? await loadRemoteNxPlugin(packageJson.name, workspaceRoot) | ||
? (await loadPlugin(packageJson.name, workspaceRoot)).plugin |
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.
See earlier lengthy comment. If this is only reason to expose loading a plugin on the main thread, lets just not. Its very little benefit to doing it and introduces all of the issues associated with loading modules during this stuff. Let's see about deferring to a package.json field like everything else.
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.
It's not worth starting up a worker for this at the moment. At least for now, it's fine to require it just for metadata
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 hear you, I'm not necessarily suggesting we start workers up, but if not then let's just drop support for list mentioning graph stuff for the time being and we can add that support back in later via a package json Metadata field or similar.
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.
Thanks for the review @AgentEnder.
return [ | ||
new Promise<RemotePlugin>((res, rej) => { | ||
worker.on('message', createWorkerHandler(worker, res, rej)); | ||
worker.on('exit', createWorkerExitHandler(worker)); | ||
}), | ||
() => { | ||
shutdownPluginWorkers(); | ||
}, | ||
] as const; |
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.
Mm.. Idk how much of a difference it makes. I'm going to keep it a tuple for the interest of time.
try { | ||
return packageJson.generators ?? | ||
packageJson.executors ?? | ||
packageJson['nx-migrations'] ?? | ||
packageJson['schematics'] ?? | ||
packageJson['builders'] | ||
? await loadRemoteNxPlugin(packageJson.name, workspaceRoot) | ||
? (await loadPlugin(packageJson.name, workspaceRoot)).plugin |
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.
It's not worth starting up a worker for this at the moment. At least for now, it's fine to require it just for metadata
}).then((val) => { | ||
// Remove the promise from the pending set | ||
pending.delete(tx); | ||
// Return the original value | ||
return val; | ||
}); |
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.
Why do we not remove promises from pending when they complete now?
try { | ||
return packageJson.generators ?? | ||
packageJson.executors ?? | ||
packageJson['nx-migrations'] ?? | ||
packageJson['schematics'] ?? | ||
packageJson['builders'] | ||
? await loadRemoteNxPlugin(packageJson.name, workspaceRoot) | ||
? (await loadPlugin(packageJson.name, workspaceRoot)).plugin |
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 hear you, I'm not necessarily suggesting we start workers up, but if not then let's just drop support for list mentioning graph stuff for the time being and we can add that support back in later via a package json Metadata field or similar.
a17ef8b
to
b1dabae
Compare
This reverts commit ec12e67.
This reverts commit ec12e67.
This pull request has already been merged/closed. If you experience issues related to these changes, please open a new issue referencing this pull request. |
Current Behavior
In the daemon, plugin workers will be loaded and shutdown concurrently by different processes. This causes requests to be unanswered and for the main process to hang.
Expected Behavior
The main Nx process does not hang and the requests are fulfilled. The daemon starts the plugin workers once and uses them until it shuts down.
Related Issue(s)
Fixes #