diff --git a/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts index af0fe2444a0ea4..db7bee7d8b05bc 100644 --- a/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts +++ b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts @@ -101,6 +101,13 @@ export async function loadRemoteNxPlugin( const cleanupFunction = () => { worker.off('exit', exitHandler); + // Unpipe streams to prevent hanging processes and release references + if (worker.stdout) { + worker.stdout.unpipe(process.stdout); + } + if (worker.stderr) { + worker.stderr.unpipe(process.stderr); + } socket.destroy(); nxPluginWorkerCache.delete(cacheKey); }; @@ -338,6 +345,13 @@ function createWorkerExitHandler( pendingPromises: Map ) { return () => { + // Clean up piped streams when worker exits to prevent hanging + if (worker.stdout) { + worker.stdout.unpipe(process.stdout); + } + if (worker.stderr) { + worker.stderr.unpipe(process.stderr); + } for (const [_, pendingPromise] of pendingPromises) { pendingPromise.rejector( new Error( @@ -444,13 +458,31 @@ async function startPluginWorker(name: string) { name, ], { - stdio: 'inherit', + stdio: ['ignore', 'pipe', 'pipe'], env, detached: true, shell: false, windowsHide: true, } ); + + // To make debugging easier and allow plugins to communicate things + // like performance metrics, we pipe the stdout/stderr of the worker + // to the main process. + // This adds one listener per plugin to a few events on process.stdout/stderr, + // so we need to increase the max listener count to avoid warnings. + // + // We originally used `inherit` for stdio, but that caused issues with + // some environments where the terminal was left in an inconsistent state + // that prevented `↑`/`↓` arrow keys from working correctly after Nx finished execution. + // Instead, they would print things like `^[[A`/`^[[B` to the terminal. + const stdoutMaxListeners = process.stdout.getMaxListeners(); + const stderrMaxListeners = process.stderr.getMaxListeners(); + process.stdout.setMaxListeners(stdoutMaxListeners + 1); + process.stderr.setMaxListeners(stderrMaxListeners + 1); + worker.stdout.pipe(process.stdout); + worker.stderr.pipe(process.stderr); + worker.unref(); let attempts = 0;