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

fix(core): only start plugin workers once #22222

Merged
merged 2 commits into from Mar 8, 2024

Conversation

FrozenPandaz
Copy link
Collaborator

@FrozenPandaz FrozenPandaz commented Mar 7, 2024

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 #

Copy link

vercel bot commented Mar 7, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Ignored Deployment
Name Status Preview Updated (UTC)
nx-dev ⬜️ Ignored (Inspect) Visit Preview Mar 8, 2024 2:27pm

@FrozenPandaz FrozenPandaz force-pushed the fix-pool2 branch 3 times, most recently from 349635f to aedac1b Compare March 8, 2024 01:43
@FrozenPandaz FrozenPandaz marked this pull request as ready for review March 8, 2024 02:52
@FrozenPandaz FrozenPandaz requested review from a team as code owners March 8, 2024 02:52
@FrozenPandaz FrozenPandaz enabled auto-merge (squash) March 8, 2024 02:55
Copy link
Member

@AgentEnder AgentEnder left a 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

Its probably fine , but is a bit messy.

Comment on lines 147 to 174
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 };
}
Copy link
Member

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 🤔 .
  }
}

Comment on lines 54 to 72
return [
new Promise<RemotePlugin>((res, rej) => {
worker.on('message', createWorkerHandler(worker, res, rej));
worker.on('exit', createWorkerExitHandler(worker));
}),
() => {
shutdownPluginWorkers();
},
] as const;
Copy link
Member

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>
}

Copy link
Collaborator Author

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();
Copy link
Member

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() {
Copy link
Member

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(
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export async function loadNxPluginsRemotely(
export async function loadNxPluginsViaWorker(

}

export async function loadNxPlugin(
export function loadNxPluginRemotely(
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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
Copy link
Member

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.

Copy link
Collaborator Author

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

Copy link
Member

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.

Copy link
Collaborator Author

@FrozenPandaz FrozenPandaz left a 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.

Comment on lines 54 to 72
return [
new Promise<RemotePlugin>((res, rej) => {
worker.on('message', createWorkerHandler(worker, res, rej));
worker.on('exit', createWorkerExitHandler(worker));
}),
() => {
shutdownPluginWorkers();
},
] as const;
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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

Comment on lines 249 to 238
}).then((val) => {
// Remove the promise from the pending set
pending.delete(tx);
// Return the original value
return val;
});
Copy link
Member

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
Copy link
Member

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.

@FrozenPandaz FrozenPandaz force-pushed the fix-pool2 branch 2 times, most recently from a17ef8b to b1dabae Compare March 8, 2024 14:24
@FrozenPandaz FrozenPandaz merged commit ec12e67 into nrwl:master Mar 8, 2024
6 checks passed
@FrozenPandaz FrozenPandaz deleted the fix-pool2 branch March 8, 2024 15:34
JamesHenry pushed a commit to JamesHenry/nx that referenced this pull request Mar 8, 2024
JamesHenry pushed a commit to JamesHenry/nx that referenced this pull request Mar 8, 2024
FrozenPandaz added a commit to FrozenPandaz/nx that referenced this pull request Mar 8, 2024
FrozenPandaz added a commit to FrozenPandaz/nx that referenced this pull request Mar 8, 2024
Copy link

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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants