## Current Behavior
Running Nx in multiple processes at the same time with the daemon
disabled can cripple a system due to excess memory usage when creating
the graph. This is due to plugin workers being started per-parent
process when there is no daemon. This change enables a file lock to
prevent the simultaneous processing, and read from the cache when the
first run completes.
Currently, running `nx show projects` 30 times in parallel looks
something like this:
30 processes exited within 37535ms
## Expected Behavior
30 processes exited within 6435ms
## Test Script
```js
//@ts-check
const { spawn } = require('child_process');
let alive = new Set();
let start = Date.now();
let iterations = 30;
for (let i = 0; i < iterations; i++) {
const cp = spawn('npx nx show projects', [], {
shell: true,
env: {
...process.env,
NX_DAEMON: 'false',
NX_VERBOSE_LOGGING: 'true',
},
});
alive.add(i);
// cp.stdout.on('data', (data) => {
// console.log(`stdout [${i}]: ${data}`);
// });
cp.stderr.on('data', (data) => {
console.error(`stderr [${i}]: ${data}`);
});
cp.on('exit', (code) => {
console.log(`child process ${i} exited with code ${code}`);
alive.delete(i);
});
}
const i = setInterval(() => {
if (alive.size > 0) {
} else {
clearInterval(i);
console.log(
`${iterations} processes exited within ${Date.now() - start}ms`
);
}
}, 1);
```
102 lines
2.9 KiB
TypeScript
102 lines
2.9 KiB
TypeScript
import {
|
|
ExecutorContext,
|
|
ProjectGraph,
|
|
readCachedProjectGraph,
|
|
Target,
|
|
} from 'nx/src/devkit-exports';
|
|
import { splitByColons, splitTarget } from 'nx/src/devkit-internals';
|
|
|
|
/**
|
|
* Parses a target string into {project, target, configuration}
|
|
*
|
|
* Examples:
|
|
* ```typescript
|
|
* parseTargetString("proj:test", graph) // returns { project: "proj", target: "test" }
|
|
* parseTargetString("proj:test:production", graph) // returns { project: "proj", target: "test", configuration: "production" }
|
|
* ```
|
|
*
|
|
* @param targetString - target reference
|
|
*/
|
|
export function parseTargetString(
|
|
targetString: string,
|
|
projectGraph: ProjectGraph
|
|
): Target;
|
|
/**
|
|
* Parses a target string into {project, target, configuration}. Passing a full
|
|
* {@link ExecutorContext} enables the targetString to reference the current project.
|
|
*
|
|
* Examples:
|
|
* ```typescript
|
|
* parseTargetString("test", executorContext) // returns { project: "proj", target: "test" }
|
|
* parseTargetString("proj:test", executorContext) // returns { project: "proj", target: "test" }
|
|
* parseTargetString("proj:test:production", executorContext) // returns { project: "proj", target: "test", configuration: "production" }
|
|
* ```
|
|
*/
|
|
export function parseTargetString(
|
|
targetString: string,
|
|
ctx: ExecutorContext
|
|
): Target;
|
|
export function parseTargetString(
|
|
targetString: string,
|
|
projectGraphOrCtx?: ProjectGraph | ExecutorContext
|
|
): Target {
|
|
let projectGraph: ProjectGraph =
|
|
projectGraphOrCtx && 'projectGraph' in projectGraphOrCtx
|
|
? projectGraphOrCtx.projectGraph
|
|
: (projectGraphOrCtx as ProjectGraph);
|
|
|
|
if (!projectGraph) {
|
|
try {
|
|
projectGraph = readCachedProjectGraph();
|
|
} catch (e) {
|
|
projectGraph = { nodes: {} } as any;
|
|
}
|
|
}
|
|
|
|
const [maybeProject] = splitByColons(targetString);
|
|
if (
|
|
!projectGraph.nodes[maybeProject] &&
|
|
projectGraphOrCtx &&
|
|
'projectName' in projectGraphOrCtx &&
|
|
maybeProject !== projectGraphOrCtx.projectName
|
|
) {
|
|
targetString = `${projectGraphOrCtx.projectName}:${targetString}`;
|
|
}
|
|
|
|
const [project, target, configuration] = splitTarget(
|
|
targetString,
|
|
projectGraph
|
|
);
|
|
|
|
if (!project || !target) {
|
|
throw new Error(`Invalid Target String: ${targetString}`);
|
|
}
|
|
return {
|
|
project,
|
|
target,
|
|
configuration,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a string in the format "project:target[:configuration]" for the target
|
|
*
|
|
* @param target - target object
|
|
*
|
|
* Examples:
|
|
*
|
|
* ```typescript
|
|
* targetToTargetString({ project: "proj", target: "test" }) // returns "proj:test"
|
|
* targetToTargetString({ project: "proj", target: "test", configuration: "production" }) // returns "proj:test:production"
|
|
* ```
|
|
*/
|
|
export function targetToTargetString({
|
|
project,
|
|
target,
|
|
configuration,
|
|
}: Target): string {
|
|
return `${project}:${target.indexOf(':') > -1 ? `"${target}"` : target}${
|
|
configuration !== undefined ? ':' + configuration : ''
|
|
}`;
|
|
}
|