From 4fc0a47a7a9a5070d1815e0555819b057c728a90 Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Thu, 8 May 2025 13:18:46 -0400 Subject: [PATCH] fix(core): add registerExitHandler utility --- .../detox/src/executors/build/build.impl.ts | 1 - .../detox/src/executors/test/test.impl.ts | 1 - .../src/executors/esbuild/esbuild.impl.ts | 15 +---- .../executors/build-list/build-list.impl.ts | 1 - .../expo/src/executors/build/build.impl.ts | 1 - .../expo/src/executors/export/export.impl.ts | 1 - .../src/executors/prebuild/prebuild.impl.ts | 1 - packages/expo/src/executors/run/run.impl.ts | 1 - .../expo/src/executors/start/start.impl.ts | 1 - .../expo/src/executors/submit/submit.impl.ts | 1 - .../expo/src/executors/update/update.impl.ts | 1 - packages/js/src/executors/node/node.impl.ts | 18 ++---- packages/js/src/executors/swc/swc.impl.ts | 5 +- .../js/src/executors/tsc/tsc.batch-impl.ts | 8 +-- packages/js/src/executors/tsc/tsc.impl.ts | 8 +-- .../src/executors/verdaccio/verdaccio.impl.ts | 3 - packages/js/src/utils/swc/compile-swc.ts | 2 - .../executors/utils/build-static-remotes.ts | 1 - ...module-federation-ssr-dev-server-plugin.ts | 3 - .../src/plugins/utils/build-static-remotes.ts | 1 - .../src/plugins/utils/start-remote-proxies.ts | 1 - .../utils/start-static-remotes-file-server.ts | 1 - .../src/utils/start-remote-proxies.ts | 1 - .../src/utils/start-ssr-remote-proxies.ts | 1 - .../next/src/executors/build/build.impl.ts | 12 ++-- .../next/src/executors/server/server.impl.ts | 3 - packages/nx/bin/nx.ts | 25 ++------ packages/nx/bin/run-executor.ts | 5 +- packages/nx/src/command-line/graph/graph.ts | 13 ++-- .../executors/run-commands/running-tasks.ts | 25 ++------ .../executors/run-script/run-script.impl.ts | 18 ++---- packages/nx/src/tasks-runner/fork.ts | 21 ++----- ...mic-run-many-terminal-output-life-cycle.ts | 8 +-- ...amic-run-one-terminal-output-life-cycle.ts | 6 +- .../nx/src/tasks-runner/pseudo-terminal.ts | 10 ++-- packages/nx/src/utils/exit-codes.ts | 13 ++++ packages/nx/src/utils/signals.ts | 59 +++++++++++++++++++ .../module-federation-static-server.impl.ts | 6 -- .../remix/src/executors/serve/serve.impl.ts | 3 - .../module-federation-static-server.impl.ts | 6 -- .../src/executors/rspack/rspack.impl.ts | 2 - .../nx-vite-build-coordination.plugin.ts | 1 - .../vite/src/executors/test/vitest.impl.ts | 2 - .../executors/file-server/file-server.impl.ts | 1 - 44 files changed, 142 insertions(+), 175 deletions(-) create mode 100644 packages/nx/src/utils/signals.ts diff --git a/packages/detox/src/executors/build/build.impl.ts b/packages/detox/src/executors/build/build.impl.ts index c1b9c309788d8..e7282a956caff 100644 --- a/packages/detox/src/executors/build/build.impl.ts +++ b/packages/detox/src/executors/build/build.impl.ts @@ -45,7 +45,6 @@ export function runCliBuild( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/detox/src/executors/test/test.impl.ts b/packages/detox/src/executors/test/test.impl.ts index fee1a4a6d64ac..8e98e2db68b16 100644 --- a/packages/detox/src/executors/test/test.impl.ts +++ b/packages/detox/src/executors/test/test.impl.ts @@ -66,7 +66,6 @@ function runCliTest( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/esbuild/src/executors/esbuild/esbuild.impl.ts b/packages/esbuild/src/executors/esbuild/esbuild.impl.ts index 16c7f1f3f949e..391cc7284dc31 100644 --- a/packages/esbuild/src/executors/esbuild/esbuild.impl.ts +++ b/packages/esbuild/src/executors/esbuild/esbuild.impl.ts @@ -173,7 +173,7 @@ export async function* esbuildExecutor( }) ); - registerCleanupCallback(() => { + process.once('exit', () => { assetsResult?.stop(); packageJsonResult?.stop(); disposeFns.forEach((fn) => fn()); @@ -269,17 +269,4 @@ async function runTypeCheck( return { errors, warnings }; } -function registerCleanupCallback(callback: () => void) { - const wrapped = () => { - callback(); - process.off('SIGINT', wrapped); - process.off('SIGTERM', wrapped); - process.off('exit', wrapped); - }; - - process.on('SIGINT', wrapped); - process.on('SIGTERM', wrapped); - process.on('exit', wrapped); -} - export default esbuildExecutor; diff --git a/packages/expo/src/executors/build-list/build-list.impl.ts b/packages/expo/src/executors/build-list/build-list.impl.ts index 3b100a6037e0c..eb93a218e245b 100644 --- a/packages/expo/src/executors/build-list/build-list.impl.ts +++ b/packages/expo/src/executors/build-list/build-list.impl.ts @@ -52,7 +52,6 @@ export function runCliBuildList( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); let output = ''; childProcess.stdout.on('data', (message) => { diff --git a/packages/expo/src/executors/build/build.impl.ts b/packages/expo/src/executors/build/build.impl.ts index d20cbd2f25339..d13aa6dc6b45d 100644 --- a/packages/expo/src/executors/build/build.impl.ts +++ b/packages/expo/src/executors/build/build.impl.ts @@ -68,7 +68,6 @@ function runCliBuild( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/expo/src/executors/export/export.impl.ts b/packages/expo/src/executors/export/export.impl.ts index 0d901f2a31ac7..b4526dadb82ce 100644 --- a/packages/expo/src/executors/export/export.impl.ts +++ b/packages/expo/src/executors/export/export.impl.ts @@ -48,7 +48,6 @@ function exportAsync( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/expo/src/executors/prebuild/prebuild.impl.ts b/packages/expo/src/executors/prebuild/prebuild.impl.ts index 6497460f857b3..2fca24e230012 100644 --- a/packages/expo/src/executors/prebuild/prebuild.impl.ts +++ b/packages/expo/src/executors/prebuild/prebuild.impl.ts @@ -55,7 +55,6 @@ export function prebuildAsync( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/expo/src/executors/run/run.impl.ts b/packages/expo/src/executors/run/run.impl.ts index 28d7af6a34f63..326498760f748 100644 --- a/packages/expo/src/executors/run/run.impl.ts +++ b/packages/expo/src/executors/run/run.impl.ts @@ -70,7 +70,6 @@ function runCliRun( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/expo/src/executors/start/start.impl.ts b/packages/expo/src/executors/start/start.impl.ts index ddf89379516cc..b3e67df3c8500 100644 --- a/packages/expo/src/executors/start/start.impl.ts +++ b/packages/expo/src/executors/start/start.impl.ts @@ -56,7 +56,6 @@ function startAsync( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/expo/src/executors/submit/submit.impl.ts b/packages/expo/src/executors/submit/submit.impl.ts index e18d03cb5bd28..149002c05b14e 100644 --- a/packages/expo/src/executors/submit/submit.impl.ts +++ b/packages/expo/src/executors/submit/submit.impl.ts @@ -47,7 +47,6 @@ function runCliSubmit( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/expo/src/executors/update/update.impl.ts b/packages/expo/src/executors/update/update.impl.ts index 3bb069403c07d..d91244ef1449c 100644 --- a/packages/expo/src/executors/update/update.impl.ts +++ b/packages/expo/src/executors/update/update.impl.ts @@ -47,7 +47,6 @@ function runCliUpdate( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', () => childProcess.kill()); childProcess.on('error', (err) => { reject(err); diff --git a/packages/js/src/executors/node/node.impl.ts b/packages/js/src/executors/node/node.impl.ts index 8e8a11d654770..3db481b1a508f 100644 --- a/packages/js/src/executors/node/node.impl.ts +++ b/packages/js/src/executors/node/node.impl.ts @@ -22,6 +22,7 @@ import { killTree } from './lib/kill-tree'; import { fileExists } from 'nx/src/utils/fileutils'; import { getRelativeDirectoryToProjectRoot } from '../../utils/get-main-file-dir'; import { interpolate } from 'nx/src/tasks-runner/utils'; +import { registerExitHandler } from 'nx/src/utils/signals'; interface ActiveTask { id: string; @@ -233,18 +234,11 @@ export async function* nodeExecutor( } }; - process.on('SIGTERM', async () => { - await stopAllTasks('SIGTERM'); - process.exit(128 + 15); - }); - process.on('SIGINT', async () => { - await stopAllTasks('SIGINT'); - process.exit(128 + 2); - }); - process.on('SIGHUP', async () => { - await stopAllTasks('SIGHUP'); - process.exit(128 + 1); - }); + for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP'] as const) { + registerExitHandler('SIGTERM', async () => { + await stopAllTasks(signal); + }); + } registerCleanup(async () => { await stopAllTasks('SIGTERM'); diff --git a/packages/js/src/executors/swc/swc.impl.ts b/packages/js/src/executors/swc/swc.impl.ts index 5a042bb74380b..22be9c537016a 100644 --- a/packages/js/src/executors/swc/swc.impl.ts +++ b/packages/js/src/executors/swc/swc.impl.ts @@ -28,6 +28,7 @@ import { compileSwc, compileSwcWatch } from '../../utils/swc/compile-swc'; import { getSwcrcPath } from '../../utils/swc/get-swcrc-path'; import { generateTmpSwcrc } from '../../utils/swc/inline'; import { isUsingTsSolutionSetup } from '../../utils/typescript/ts-solution-setup'; +import { registerExitHandler } from 'nx/src/utils/signals'; function normalizeOptions( options: SwcExecutorOptions, @@ -186,8 +187,8 @@ export async function* swcExecutor( if (options.watch) { let disposeFn: () => void; - process.on('SIGINT', () => disposeFn()); - process.on('SIGTERM', () => disposeFn()); + registerExitHandler('SIGINT', (s) => disposeFn()); + registerExitHandler('SIGTERM', (s) => disposeFn()); return yield* compileSwcWatch(context, options, async () => { const assetResult = await copyAssets(options, context); diff --git a/packages/js/src/executors/tsc/tsc.batch-impl.ts b/packages/js/src/executors/tsc/tsc.batch-impl.ts index 57e39621f5f73..3925a5a9076d6 100644 --- a/packages/js/src/executors/tsc/tsc.batch-impl.ts +++ b/packages/js/src/executors/tsc/tsc.batch-impl.ts @@ -27,6 +27,7 @@ import { watchTaskProjectsPackageJsonFileChanges, } from './lib/batch'; import { createEntryPoints } from '../../utils/package-json/create-entry-points'; +import { registerExitHandler } from 'nx/src/utils/signals'; export async function* tscBatchExecutor( taskGraph: TaskGraph, @@ -150,13 +151,12 @@ export async function* tscBatchExecutor( } ); - const handleTermination = async (exitCode: number) => { + const handleTermination = async () => { watchAssetsChangesDisposer(); watchProjectsChangesDisposer(); - process.exit(exitCode); }; - process.on('SIGINT', () => handleTermination(128 + 2)); - process.on('SIGTERM', () => handleTermination(128 + 15)); + registerExitHandler('SIGINT', (s) => handleTermination()); + registerExitHandler('SIGTERM', (s) => handleTermination()); return yield* mapAsyncIterable(typescriptCompilation, async (iterator) => { // drain the iterator, we don't use the results diff --git a/packages/js/src/executors/tsc/tsc.impl.ts b/packages/js/src/executors/tsc/tsc.impl.ts index 777fc219aad29..e094f5ffb64bc 100644 --- a/packages/js/src/executors/tsc/tsc.impl.ts +++ b/packages/js/src/executors/tsc/tsc.impl.ts @@ -24,6 +24,7 @@ import { watchForSingleFileChanges } from '../../utils/watch-for-single-file-cha import { getCustomTrasformersFactory, normalizeOptions } from './lib'; import { readTsConfig } from '../../utils/typescript/ts-config'; import { createEntryPoints } from '../../utils/package-json/create-entry-points'; +import { registerExitHandler } from 'nx/src/utils/signals'; export function determineModuleFormatFromTsConfig( absolutePathToTsConfig: string @@ -169,14 +170,13 @@ export async function* tscExecutor( ) ); } - const handleTermination = async (exitCode: number) => { + const handleTermination = async () => { await typescriptCompilation.close(); disposeWatchAssetChanges(); disposePackageJsonChanges?.(); - process.exit(exitCode); }; - process.on('SIGINT', () => handleTermination(128 + 2)); - process.on('SIGTERM', () => handleTermination(128 + 15)); + registerExitHandler('SIGINT', (s) => handleTermination()); + registerExitHandler('SIGTERM', (s) => handleTermination()); } return yield* typescriptCompilation.iterator; diff --git a/packages/js/src/executors/verdaccio/verdaccio.impl.ts b/packages/js/src/executors/verdaccio/verdaccio.impl.ts index 563b4425b0d69..b6a11237e0ba3 100644 --- a/packages/js/src/executors/verdaccio/verdaccio.impl.ts +++ b/packages/js/src/executors/verdaccio/verdaccio.impl.ts @@ -57,9 +57,6 @@ export async function verdaccioExecutor( } }; process.on('exit', processExitListener); - process.on('SIGTERM', processExitListener); - process.on('SIGINT', processExitListener); - process.on('SIGHUP', processExitListener); try { await startVerdaccio(options, context.root); diff --git a/packages/js/src/utils/swc/compile-swc.ts b/packages/js/src/utils/swc/compile-swc.ts index 2a6f0801c7ede..228e0962322dd 100644 --- a/packages/js/src/utils/swc/compile-swc.ts +++ b/packages/js/src/utils/swc/compile-swc.ts @@ -229,8 +229,6 @@ export async function* compileSwcWatch( swcWatcher.stdout.on('data', stdoutOnData); swcWatcher.stderr.on('data', stderrOnData); - process.on('SIGINT', processOnExit); - process.on('SIGTERM', processOnExit); process.on('exit', processOnExit); swcWatcher.on('exit', watcherOnExit); diff --git a/packages/module-federation/src/executors/utils/build-static-remotes.ts b/packages/module-federation/src/executors/utils/build-static-remotes.ts index b99d8fd6610c6..31b05d6903d0c 100644 --- a/packages/module-federation/src/executors/utils/build-static-remotes.ts +++ b/packages/module-federation/src/executors/utils/build-static-remotes.ts @@ -87,7 +87,6 @@ export async function buildStaticRemotes( res(); } }); - process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); process.on('exit', () => staticProcess.kill('SIGTERM')); }); diff --git a/packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-ssr-dev-server-plugin.ts b/packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-ssr-dev-server-plugin.ts index b1cd9b262cccd..84cda82c59306 100644 --- a/packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-ssr-dev-server-plugin.ts +++ b/packages/module-federation/src/plugins/nx-module-federation-plugin/rspack/nx-module-federation-ssr-dev-server-plugin.ts @@ -130,9 +130,6 @@ export class NxModuleFederationSSRDevServerPlugin process.on('exit', () => { this.devServerProcess?.kill('SIGKILL'); }); - process.on('SIGINT', () => { - this.devServerProcess?.kill('SIGKILL'); - }); callback(); }); } diff --git a/packages/module-federation/src/plugins/utils/build-static-remotes.ts b/packages/module-federation/src/plugins/utils/build-static-remotes.ts index 2e6a7a35e6ba8..2115230aeb13b 100644 --- a/packages/module-federation/src/plugins/utils/build-static-remotes.ts +++ b/packages/module-federation/src/plugins/utils/build-static-remotes.ts @@ -76,7 +76,6 @@ export async function buildStaticRemotes( res(); } }); - process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); process.on('exit', () => staticProcess.kill('SIGTERM')); }); diff --git a/packages/module-federation/src/plugins/utils/start-remote-proxies.ts b/packages/module-federation/src/plugins/utils/start-remote-proxies.ts index 04c8e3fe58c01..36676aacd3cfb 100644 --- a/packages/module-federation/src/plugins/utils/start-remote-proxies.ts +++ b/packages/module-federation/src/plugins/utils/start-remote-proxies.ts @@ -55,7 +55,6 @@ export function startRemoteProxies( const proxyServer = (sslCert ? https : http) .createServer({ cert: sslCert, key: sslKey }, expressProxy) .listen(appConfig.port); - process.on('SIGTERM', () => proxyServer.close()); process.on('exit', () => proxyServer.close()); } console.info(`NX Static remotes proxies started successfully`); diff --git a/packages/module-federation/src/plugins/utils/start-static-remotes-file-server.ts b/packages/module-federation/src/plugins/utils/start-static-remotes-file-server.ts index 16f57913f2108..ba3e162f5d498 100644 --- a/packages/module-federation/src/plugins/utils/start-static-remotes-file-server.ts +++ b/packages/module-federation/src/plugins/utils/start-static-remotes-file-server.ts @@ -58,6 +58,5 @@ export function startStaticRemotesFileServer( }, } ); - process.on('SIGTERM', () => httpServerProcess.kill('SIGTERM')); process.on('exit', () => httpServerProcess.kill('SIGTERM')); } diff --git a/packages/module-federation/src/utils/start-remote-proxies.ts b/packages/module-federation/src/utils/start-remote-proxies.ts index 953dcc35b6d5d..99667a91bd370 100644 --- a/packages/module-federation/src/utils/start-remote-proxies.ts +++ b/packages/module-federation/src/utils/start-remote-proxies.ts @@ -41,7 +41,6 @@ export function startRemoteProxies( const proxyServer = (sslCert ? https : http) .createServer({ cert: sslCert, key: sslKey }, expressProxy) .listen(staticRemotesConfig.config[app].port); - process.on('SIGTERM', () => proxyServer.close()); process.on('exit', () => proxyServer.close()); } logger.info(`NX Static remotes proxies started successfully`); diff --git a/packages/module-federation/src/utils/start-ssr-remote-proxies.ts b/packages/module-federation/src/utils/start-ssr-remote-proxies.ts index 13e6a70a2a006..87f48e7ebc890 100644 --- a/packages/module-federation/src/utils/start-ssr-remote-proxies.ts +++ b/packages/module-federation/src/utils/start-ssr-remote-proxies.ts @@ -58,7 +58,6 @@ export function startSsrRemoteProxies( const proxyServer = (sslCert ? https : http) .createServer({ cert: sslCert, key: sslKey }, expressProxy) .listen(staticRemotesConfig.config[app].port); - process.on('SIGTERM', () => proxyServer.close()); process.on('exit', () => proxyServer.close()); } logger.info(`Nx SSR Static remotes proxies started successfully`); diff --git a/packages/next/src/executors/build/build.impl.ts b/packages/next/src/executors/build/build.impl.ts index f5e869a54d268..9144a3369c175 100644 --- a/packages/next/src/executors/build/build.impl.ts +++ b/packages/next/src/executors/build/build.impl.ts @@ -20,6 +20,7 @@ import { NextBuildBuilderOptions } from '../../utils/types'; import { ChildProcess, fork } from 'child_process'; import { createCliOptions } from '../../utils/create-cli-options'; import { signalToCode } from 'nx/src/utils/exit-codes'; +import { registerExitHandler } from 'nx/src/utils/signals'; let childProcess: ChildProcess; @@ -157,12 +158,11 @@ function runCliBuild( // Ensure the child process is killed when the parent exits process.on('exit', () => childProcess.kill()); - process.on('SIGTERM', (signal) => { - reject({ code: signalToCode(signal), signal }); - }); - process.on('SIGINT', (signal) => { - reject({ code: signalToCode(signal), signal }); - }); + for (const signal of ['SIGINT', 'SIGTERM'] as const) { + registerExitHandler(signal, () => { + reject({ code: signalToCode(signal), signal }); + }); + } childProcess.on('error', (err) => { reject({ error: err }); diff --git a/packages/next/src/executors/server/server.impl.ts b/packages/next/src/executors/server/server.impl.ts index c7f6b4da64f77..e667872aec717 100644 --- a/packages/next/src/executors/server/server.impl.ts +++ b/packages/next/src/executors/server/server.impl.ts @@ -82,9 +82,6 @@ export default async function* serveExecutor( } }; process.on('exit', () => killServer()); - process.on('SIGINT', () => killServer()); - process.on('SIGTERM', () => killServer()); - process.on('SIGHUP', () => killServer()); await waitForPortOpen(options.port, { host: options.hostname }); diff --git a/packages/nx/bin/nx.ts b/packages/nx/bin/nx.ts index d8a6a0f62bea4..5887dd9731381 100644 --- a/packages/nx/bin/nx.ts +++ b/packages/nx/bin/nx.ts @@ -21,7 +21,7 @@ import { performance } from 'perf_hooks'; import { setupWorkspaceContext } from '../src/utils/workspace-context'; import { daemonClient } from '../src/daemon/client/client'; import { removeDbConnections } from '../src/utils/db-connection'; -import { signalToCode } from '../src/utils/exit-codes'; +import { registerExitHandler } from '../src/utils/signals'; // In case Nx Cloud forcibly exits while the TUI is running, ensure the terminal is restored etc. process.on('exit', (...args) => { @@ -283,26 +283,13 @@ const getLatestVersionOfNx = ((fn: () => string) => { return () => cache || (cache = fn()); })(_getLatestVersionOfNx); -function nxCleanup(signal?: NodeJS.Signals) { +function nxCleanup() { removeDbConnections(); - if (signal) { - process.exit(signalToCode(signal)); - } else { - process.exit(); - } } -process.on('exit', () => { - nxCleanup(); -}); -process.on('SIGINT', () => { - nxCleanup('SIGINT'); -}); -process.on('SIGTERM', () => { - nxCleanup('SIGTERM'); -}); -process.on('SIGHUP', () => { - nxCleanup('SIGHUP'); -}); +registerExitHandler('SIGINT', nxCleanup); +registerExitHandler('SIGTERM', nxCleanup); +registerExitHandler('SIGHUP', nxCleanup); +registerExitHandler('exit', nxCleanup); main(); diff --git a/packages/nx/bin/run-executor.ts b/packages/nx/bin/run-executor.ts index 7b43b2bb7a1fd..e5943a6ac52fb 100644 --- a/packages/nx/bin/run-executor.ts +++ b/packages/nx/bin/run-executor.ts @@ -1,7 +1,10 @@ import { appendFileSync, openSync, writeFileSync } from 'fs'; -import { Target, run } from '../src/command-line/run/run'; import { TaskGraph } from '../src/config/task-graph'; +import { Target, run } from '../src/command-line/run/run'; + +import '../src/utils/signals'; + if (process.env.NX_TERMINAL_OUTPUT_PATH) { setUpOutputWatching( process.env.NX_TERMINAL_CAPTURE_STDERR === 'true', diff --git a/packages/nx/src/command-line/graph/graph.ts b/packages/nx/src/command-line/graph/graph.ts index 5e172ab617cd6..16daa5a08550b 100644 --- a/packages/nx/src/command-line/graph/graph.ts +++ b/packages/nx/src/command-line/graph/graph.ts @@ -57,6 +57,7 @@ import { ConfigurationSourceMaps } from '../../project-graph/utils/project-confi import { createTaskHasher } from '../../hasher/create-task-hasher'; import { ProjectGraphError } from '../../project-graph/error-types'; import { isNxCloudUsed } from '../../utils/nx-cloud-utils'; +import { registerExitHandler } from '../../utils/signals'; export interface GraphError { message: string; @@ -667,14 +668,18 @@ async function startServer( } }); - const handleTermination = async (exitCode: number) => { + const handleTermination = async () => { if (unregisterFileWatcher) { unregisterFileWatcher(); } - process.exit(exitCode); }; - process.on('SIGINT', () => handleTermination(128 + 2)); - process.on('SIGTERM', () => handleTermination(128 + 15)); + + registerExitHandler('SIGINT', () => { + handleTermination(); + }); + registerExitHandler('SIGTERM', () => { + handleTermination(); + }); return new Promise<{ app: Server; url: URL }>((res) => { app.listen(port, host, () => { diff --git a/packages/nx/src/executors/run-commands/running-tasks.ts b/packages/nx/src/executors/run-commands/running-tasks.ts index b3420c0e90d5f..ecd33d13ddad5 100644 --- a/packages/nx/src/executors/run-commands/running-tasks.ts +++ b/packages/nx/src/executors/run-commands/running-tasks.ts @@ -14,12 +14,12 @@ import { loadAndExpandDotEnvFile, unloadDotEnvFile, } from '../../tasks-runner/task-env'; -import { signalToCode } from '../../utils/exit-codes'; import { LARGE_BUFFER, NormalizedRunCommandsOptions, RunCommandsCommandOptions, } from './run-commands.impl'; +import { registerExitHandler } from '../../utils/signals'; export class ParallelRunningTasks implements RunningTask { private readonly childProcesses: RunningNodeProcess[]; @@ -594,23 +594,8 @@ function registerProcessListener( } }); - // Terminate any task processes on exit - process.on('exit', () => { - runningTask.kill(); - }); - process.on('SIGINT', () => { - runningTask.kill('SIGTERM'); - // we exit here because we don't need to write anything to cache. - process.exit(signalToCode('SIGINT')); - }); - process.on('SIGTERM', () => { - runningTask.kill('SIGTERM'); - // no exit here because we expect child processes to terminate which - // will store results to the cache and will terminate this process - }); - process.on('SIGHUP', () => { - runningTask.kill('SIGTERM'); - // no exit here because we expect child processes to terminate which - // will store results to the cache and will terminate this process - }); + registerExitHandler('exit', () => runningTask.kill()); + registerExitHandler('SIGINT', () => runningTask.kill('SIGINT')); + registerExitHandler('SIGTERM', () => runningTask.kill('SIGTERM')); + registerExitHandler('SIGHUP', () => runningTask.kill('SIGHUP')); } diff --git a/packages/nx/src/executors/run-script/run-script.impl.ts b/packages/nx/src/executors/run-script/run-script.impl.ts index a3a9c9c797a33..53f77c163e1b5 100644 --- a/packages/nx/src/executors/run-script/run-script.impl.ts +++ b/packages/nx/src/executors/run-script/run-script.impl.ts @@ -7,7 +7,7 @@ import { PseudoTtyProcess, } from '../../tasks-runner/pseudo-terminal'; import { getPackageManagerCommand } from '../../utils/package-manager'; -import { signalToCode } from '../../utils/exit-codes'; +import { registerExitHandler } from '../../utils/signals'; export interface RunScriptOptions { script: string; @@ -84,16 +84,6 @@ async function ptyProcess( }); } -// TODO: This only works because pseudo terminal registers signal handlers first but we need a service to handle this -process.on('SIGHUP', () => { - cp.kill('SIGHUP'); - process.exit(signalToCode('SIGHUP')); -}); -process.on('SIGTERM', () => { - cp.kill('SIGTERM'); - process.exit(signalToCode('SIGTERM')); -}); -process.on('SIGINT', () => { - cp.kill('SIGINT'); - process.exit(signalToCode('SIGINT')); -}); +registerExitHandler('SIGINT', () => cp.kill('SIGINT')); +registerExitHandler('SIGTERM', () => cp.kill('SIGTERM')); +registerExitHandler('SIGHUP', () => cp.kill('SIGHUP')); diff --git a/packages/nx/src/tasks-runner/fork.ts b/packages/nx/src/tasks-runner/fork.ts index a8aa72846e719..625099df3b1a3 100644 --- a/packages/nx/src/tasks-runner/fork.ts +++ b/packages/nx/src/tasks-runner/fork.ts @@ -1,7 +1,7 @@ import { fork, Serializable } from 'child_process'; import { join } from 'path'; import { PseudoIPCClient } from './pseudo-ipc'; -import { signalToCode } from '../utils/exit-codes'; +import { registerExitHandler } from '../utils/signals'; const pseudoIPCPath = process.argv[2]; const forkId = process.argv[3]; @@ -37,17 +37,8 @@ childProcess.on('exit', (code) => { process.exit(code); }); -// Terminate the child process when exiting -process.on('exit', () => { - childProcess.kill(); -}); -process.on('SIGINT', () => { - childProcess.kill('SIGTERM'); - process.exit(signalToCode('SIGINT')); -}); -process.on('SIGTERM', () => { - childProcess.kill('SIGTERM'); -}); -process.on('SIGHUP', () => { - childProcess.kill('SIGTERM'); -}); +registerExitHandler('exit', () => childProcess.kill()); + +for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) { + registerExitHandler(signal, () => childProcess.kill(signal)); +} diff --git a/packages/nx/src/tasks-runner/life-cycles/dynamic-run-many-terminal-output-life-cycle.ts b/packages/nx/src/tasks-runner/life-cycles/dynamic-run-many-terminal-output-life-cycle.ts index 0202e3da5fad7..07a7bfdfeda8d 100644 --- a/packages/nx/src/tasks-runner/life-cycles/dynamic-run-many-terminal-output-life-cycle.ts +++ b/packages/nx/src/tasks-runner/life-cycles/dynamic-run-many-terminal-output-life-cycle.ts @@ -9,6 +9,7 @@ import { Task } from '../../config/task-graph'; import { prettyTime } from './pretty-time'; import { formatFlags, formatTargetsAndProjects } from './formatting-utils'; import { viewLogsFooterRows } from './view-logs-utils'; +import { registerExitHandler } from '../../utils/signals'; const LEFT_PAD = ` `; const SPACER = ` `; @@ -54,10 +55,9 @@ export async function createRunManyDynamicOutputRenderer({ } } - process.on('exit', () => clearRenderInterval()); - process.on('SIGINT', () => clearRenderInterval()); - process.on('SIGTERM', () => clearRenderInterval()); - process.on('SIGHUP', () => clearRenderInterval()); + for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP', 'exit'] as const) { + registerExitHandler(signal, () => clearRenderInterval()); + } const lifeCycle = {} as Partial; const isVerbose = overrides.verbose === true; diff --git a/packages/nx/src/tasks-runner/life-cycles/dynamic-run-one-terminal-output-life-cycle.ts b/packages/nx/src/tasks-runner/life-cycles/dynamic-run-one-terminal-output-life-cycle.ts index d0d9465d1542b..36d7d09f28b4a 100644 --- a/packages/nx/src/tasks-runner/life-cycles/dynamic-run-one-terminal-output-life-cycle.ts +++ b/packages/nx/src/tasks-runner/life-cycles/dynamic-run-one-terminal-output-life-cycle.ts @@ -8,6 +8,7 @@ import { prettyTime } from './pretty-time'; import { Task } from '../../config/task-graph'; import { formatFlags, formatTargetsAndProjects } from './formatting-utils'; import { viewLogsFooterRows } from './view-logs-utils'; +import { registerExitHandler } from '../../utils/signals'; const LEFT_PAD = ` `; const SPACER = ` `; @@ -65,10 +66,7 @@ export async function createRunOneDynamicOutputRenderer({ } } - process.on('exit', () => clearRenderInterval()); - process.on('SIGINT', () => clearRenderInterval()); - process.on('SIGTERM', () => clearRenderInterval()); - process.on('SIGHUP', () => clearRenderInterval()); + registerExitHandler('exit', () => clearRenderInterval()); const lifeCycle = {} as Partial; diff --git a/packages/nx/src/tasks-runner/pseudo-terminal.ts b/packages/nx/src/tasks-runner/pseudo-terminal.ts index 68d32caeb652b..3e0b3f892926f 100644 --- a/packages/nx/src/tasks-runner/pseudo-terminal.ts +++ b/packages/nx/src/tasks-runner/pseudo-terminal.ts @@ -4,19 +4,21 @@ import { getForkedProcessOsSocketPath } from '../daemon/socket-utils'; import { ChildProcess, IS_WASM, RustPseudoTerminal } from '../native'; import { PseudoIPCServer } from './pseudo-ipc'; import { RunningTask } from './running-tasks/running-task'; +import { registerExitHandler } from '../utils/signals'; // Register single event listeners for all pseudo-terminal instances const pseudoTerminalShutdownCallbacks: Array<(s?: NodeJS.Signals) => void> = []; -process.on('SIGINT', () => { + +registerExitHandler('SIGINT', () => { pseudoTerminalShutdownCallbacks.forEach((cb) => cb('SIGINT')); }); -process.on('SIGTERM', () => { +registerExitHandler('SIGTERM', () => { pseudoTerminalShutdownCallbacks.forEach((cb) => cb('SIGTERM')); }); -process.on('SIGHUP', () => { +registerExitHandler('SIGHUP', () => { pseudoTerminalShutdownCallbacks.forEach((cb) => cb('SIGHUP')); }); -process.on('exit', () => { +registerExitHandler('exit', () => { pseudoTerminalShutdownCallbacks.forEach((cb) => cb()); }); diff --git a/packages/nx/src/utils/exit-codes.ts b/packages/nx/src/utils/exit-codes.ts index 6ef982167f740..8e6cab2b9bf0b 100644 --- a/packages/nx/src/utils/exit-codes.ts +++ b/packages/nx/src/utils/exit-codes.ts @@ -14,3 +14,16 @@ export function signalToCode(signal: NodeJS.Signals): number { return 128; } } + +export function codeToSignal(code: number): NodeJS.Signals { + switch (code) { + case 128 + 1: + return 'SIGHUP'; + case 128 + 2: + return 'SIGINT'; + case 128 + 15: + return 'SIGTERM'; + default: + return 'SIGKILL'; + } +} diff --git a/packages/nx/src/utils/signals.ts b/packages/nx/src/utils/signals.ts new file mode 100644 index 0000000000000..9e03d8e1138e1 --- /dev/null +++ b/packages/nx/src/utils/signals.ts @@ -0,0 +1,59 @@ +import { signalToCode } from './exit-codes'; + +const exitEvents = [ + 'SIGINT', + 'SIGTERM', + 'SIGQUIT', + 'SIGHUP', + 'SIGKILL', + 'exit', +] as const; +type ExitEvent = (typeof exitEvents)[number]; + +const signalHandlers: Partial< + Record< + ExitEvent, + Array<(signalOrCode: string | number) => void | Promise> + > +> = {}; +const registeredHandlers = new Set(); + +export function registerExitHandler( + event: 'exit', + cb: (code: number) => any | Promise +); +export function registerExitHandler>( + event: T, + cb: (signal: T) => any | Promise +); +export function registerExitHandler( + event: T, + cb: (signalOrCode: T extends Omit ? T : number) => void +) { + signalHandlers[event] ??= []; + if (registeredHandlers.has(event)) { + return; + } + registerHandler(event); +} + +function registerHandler(event: ExitEvent) { + registeredHandlers.add(event); + process.on(event, async (code?: number) => { + for (const cb of signalHandlers[event]) { + let next = cb(event === 'exit' ? code : event); + if (next instanceof Promise) { + await next; + } + } + if (event === 'exit') { + // process is already exiting, don't need to exit + } else { + process.exit(signalToCode(event)); + } + }); +} + +for (const event of exitEvents) { + registerHandler(event); +} diff --git a/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts b/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts index 4c548e2d12369..bd538460c7612 100644 --- a/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts +++ b/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts @@ -127,7 +127,6 @@ async function buildHost( } }); - process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); process.on('exit', () => staticProcess.kill('SIGTERM')); }); } @@ -160,9 +159,6 @@ function moveToTmpDirectory( const cleanup = () => { rmSync(commonOutputDirectory, { force: true, recursive: true }); }; - process.on('SIGTERM', () => { - cleanup(); - }); process.on('exit', () => { cleanup(); }); @@ -210,7 +206,6 @@ export function startProxies( const proxyServer = (sslCert ? https : http) .createServer({ cert: sslCert, key: sslKey }, expressProxy) .listen(staticRemotesConfig.config[app].port); - process.on('SIGTERM', () => proxyServer.close()); process.on('exit', () => proxyServer.close()); } logger.info(`NX Static Producers (remotes) proxies started successfully`); @@ -236,7 +231,6 @@ export function startProxies( const proxyServer = (sslCert ? https : http) .createServer({ cert: sslCert, key: sslKey }, expressProxy) .listen(hostServeOptions.port); - process.on('SIGTERM', () => proxyServer.close()); process.on('exit', () => proxyServer.close()); logger.info('NX Static Consumer (host) proxy started successfully'); } diff --git a/packages/remix/src/executors/serve/serve.impl.ts b/packages/remix/src/executors/serve/serve.impl.ts index 044e7b7a07942..3b0d0b677ce38 100644 --- a/packages/remix/src/executors/serve/serve.impl.ts +++ b/packages/remix/src/executors/serve/serve.impl.ts @@ -81,9 +81,6 @@ export default async function* serveExecutor( } }; process.on('exit', () => killServer()); - process.on('SIGINT', () => killServer()); - process.on('SIGTERM', () => killServer()); - process.on('SIGHUP', () => killServer()); await waitForPortOpen(options.port); diff --git a/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts b/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts index 5c93f1dc1c9c3..af19e49817692 100644 --- a/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts +++ b/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts @@ -127,7 +127,6 @@ async function buildHost( } }); - process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); process.on('exit', () => staticProcess.kill('SIGTERM')); }); } @@ -160,9 +159,6 @@ function moveToTmpDirectory( const cleanup = () => { rmSync(commonOutputDirectory, { force: true, recursive: true }); }; - process.on('SIGTERM', () => { - cleanup(); - }); process.on('exit', () => { cleanup(); }); @@ -214,7 +210,6 @@ export function startProxies( const proxyServer = (sslCert ? https : http) .createServer({ cert: sslCert, key: sslKey }, expressProxy) .listen(staticRemotesConfig.config[app].port); - process.on('SIGTERM', () => proxyServer.close()); process.on('exit', () => proxyServer.close()); } logger.info(`NX Static remotes proxies started successfully`); @@ -240,7 +235,6 @@ export function startProxies( const proxyServer = (sslCert ? https : http) .createServer({ cert: sslCert, key: sslKey }, expressProxy) .listen(hostServeOptions.port); - process.on('SIGTERM', () => proxyServer.close()); process.on('exit', () => proxyServer.close()); logger.info('NX Static host proxy started successfully'); } diff --git a/packages/rspack/src/executors/rspack/rspack.impl.ts b/packages/rspack/src/executors/rspack/rspack.impl.ts index b42001f3192a2..09c6dae482dab 100644 --- a/packages/rspack/src/executors/rspack/rspack.impl.ts +++ b/packages/rspack/src/executors/rspack/rspack.impl.ts @@ -129,8 +129,6 @@ function registerCleanupCallback(callback: () => void) { process.off('exit', wrapped); }; - process.on('SIGINT', wrapped); - process.on('SIGTERM', wrapped); process.on('exit', wrapped); } diff --git a/packages/vite/plugins/nx-vite-build-coordination.plugin.ts b/packages/vite/plugins/nx-vite-build-coordination.plugin.ts index 82ad7e8166866..940887d93e224 100644 --- a/packages/vite/plugins/nx-vite-build-coordination.plugin.ts +++ b/packages/vite/plugins/nx-vite-build-coordination.plugin.ts @@ -70,7 +70,6 @@ export function nxViteBuildCoordinationPlugin( if (daemonClient.enabled()) { unregisterFileWatcher = await createFileWatcher(); process.on('exit', () => unregisterFileWatcher()); - process.on('SIGINT', () => process.exit()); } else { output.warn({ title: diff --git a/packages/vite/src/executors/test/vitest.impl.ts b/packages/vite/src/executors/test/vitest.impl.ts index 302c23a609cb9..fc5e3d8ba50de 100644 --- a/packages/vite/src/executors/test/vitest.impl.ts +++ b/packages/vite/src/executors/test/vitest.impl.ts @@ -52,8 +52,6 @@ export async function* vitestExecutor( }; if (watch) { - process.on('SIGINT', processExit); - process.on('SIGTERM', processExit); process.on('exit', processExit); } diff --git a/packages/web/src/executors/file-server/file-server.impl.ts b/packages/web/src/executors/file-server/file-server.impl.ts index 100d92ad409d3..38e7be3d18995 100644 --- a/packages/web/src/executors/file-server/file-server.impl.ts +++ b/packages/web/src/executors/file-server/file-server.impl.ts @@ -250,7 +250,6 @@ export default async function* fileServerExecutor( } }; process.on('exit', processExitListener); - process.on('SIGTERM', processExitListener); serve.stdout.on('data', (chunk) => { if (chunk.toString().indexOf('GET') === -1) {