{"version":3,"file":"SubprocessTerminator.js","sourceRoot":"","sources":["../src/SubprocessTerminator.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type * as child_process from 'child_process';\nimport process from 'process';\n\nimport { Executable } from './Executable';\n\n/**\n * Details about how the `child_process.ChildProcess` was created.\n *\n * @beta\n */\nexport interface ISubprocessOptions {\n /**\n * Whether or not the child process was started in detached mode.\n *\n * @remarks\n * On POSIX systems, detached=true is required for killing the subtree. Attempting to kill the\n * subtree on POSIX systems with detached=false will throw an error. On Windows, detached=true\n * creates a separate console window and is not required for killing the subtree. In general,\n * it is recommended to use SubprocessTerminator.RECOMMENDED_OPTIONS when forking or spawning\n * a child process.\n */\n detached: boolean;\n}\n\ninterface ITrackedSubprocess {\n subprocess: child_process.ChildProcess;\n subprocessOptions: ISubprocessOptions;\n}\n\n/**\n * When a child process is created, registering it with the SubprocessTerminator will ensure\n * that the child gets terminated when the current process terminates.\n *\n * @remarks\n * This works by hooking the current process's events for SIGTERM/SIGINT/exit, and ensuring the\n * child process gets terminated in those cases.\n *\n * SubprocessTerminator doesn't do anything on Windows, since by default Windows automatically\n * terminates child processes when their parent is terminated.\n *\n * @beta\n */\nexport class SubprocessTerminator {\n /**\n * Whether the hooks are installed\n */\n private static _initialized: boolean = false;\n\n /**\n * The list of registered child processes. Processes are removed from this set if they\n * terminate on their own.\n */\n private static _subprocessesByPid: Map = new Map();\n\n private static readonly _isWindows: boolean = process.platform === 'win32';\n\n /**\n * The recommended options when creating a child process.\n */\n public static readonly RECOMMENDED_OPTIONS: ISubprocessOptions = {\n detached: process.platform !== 'win32'\n };\n\n /**\n * Registers a child process so that it will be terminated automatically if the current process\n * is terminated.\n */\n public static killProcessTreeOnExit(\n subprocess: child_process.ChildProcess,\n subprocessOptions: ISubprocessOptions\n ): void {\n if (typeof subprocess.exitCode === 'number') {\n // Process has already been killed\n return;\n }\n\n SubprocessTerminator._validateSubprocessOptions(subprocessOptions);\n\n SubprocessTerminator._ensureInitialized();\n\n // Closure variable\n const pid: number | undefined = subprocess.pid;\n if (pid === undefined) {\n // The process failed to spawn.\n return;\n }\n\n subprocess.on('close', (exitCode: number | null, signal: NodeJS.Signals | null): void => {\n if (SubprocessTerminator._subprocessesByPid.delete(pid)) {\n SubprocessTerminator._logDebug(`untracking #${pid}`);\n }\n });\n SubprocessTerminator._subprocessesByPid.set(pid, {\n subprocess,\n subprocessOptions\n });\n\n SubprocessTerminator._logDebug(`tracking #${pid}`);\n }\n\n /**\n * Terminate the child process and all of its children.\n */\n public static killProcessTree(\n subprocess: child_process.ChildProcess,\n subprocessOptions: ISubprocessOptions\n ): void {\n const pid: number | undefined = subprocess.pid;\n if (pid === undefined) {\n // The process failed to spawn.\n return;\n }\n\n // Don't attempt to kill the same process twice\n if (SubprocessTerminator._subprocessesByPid.delete(pid)) {\n SubprocessTerminator._logDebug(`untracking #${pid} via killProcessTree()`);\n }\n\n SubprocessTerminator._validateSubprocessOptions(subprocessOptions);\n\n if (typeof subprocess.exitCode === 'number') {\n // Process has already been killed\n return;\n }\n\n SubprocessTerminator._logDebug(`terminating #${pid}`);\n\n if (SubprocessTerminator._isWindows) {\n // On Windows we have a problem that CMD.exe launches child processes, but when CMD.exe is killed\n // the child processes may continue running. Also if we send signals to CMD.exe the child processes\n // will not receive them. The safest solution is not to attempt a graceful shutdown, but simply\n // kill the entire process tree.\n const result: child_process.SpawnSyncReturns = Executable.spawnSync('TaskKill.exe', [\n '/T', // \"Terminates the specified process and any child processes which were started by it.\"\n '/F', // Without this, TaskKill will try to use WM_CLOSE which doesn't work with CLI tools\n '/PID',\n pid.toString()\n ]);\n\n if (result.status) {\n const output: string = result.output.join('\\n');\n // Nonzero exit code\n if (output.indexOf('not found') >= 0) {\n // The PID does not exist\n } else {\n // Another error occurred, for example TaskKill.exe does not support\n // the expected CLI syntax\n throw new Error(`TaskKill.exe returned exit code ${result.status}:\\n` + output + '\\n');\n }\n }\n } else {\n // Passing a negative PID terminates the entire group instead of just the one process\n process.kill(-pid, 'SIGKILL');\n }\n }\n\n // Install the hooks\n private static _ensureInitialized(): void {\n if (!SubprocessTerminator._initialized) {\n SubprocessTerminator._initialized = true;\n\n SubprocessTerminator._logDebug('initialize');\n\n process.prependListener('SIGTERM', SubprocessTerminator._onTerminateSignal);\n process.prependListener('SIGINT', SubprocessTerminator._onTerminateSignal);\n\n process.prependListener('exit', SubprocessTerminator._onExit);\n }\n }\n\n // Uninstall the hooks and perform cleanup\n private static _cleanupChildProcesses(): void {\n if (SubprocessTerminator._initialized) {\n SubprocessTerminator._initialized = false;\n\n process.removeListener('SIGTERM', SubprocessTerminator._onTerminateSignal);\n process.removeListener('SIGINT', SubprocessTerminator._onTerminateSignal);\n\n const trackedSubprocesses: ITrackedSubprocess[] = Array.from(\n SubprocessTerminator._subprocessesByPid.values()\n );\n\n let firstError: Error | undefined = undefined;\n\n for (const trackedSubprocess of trackedSubprocesses) {\n try {\n SubprocessTerminator.killProcessTree(trackedSubprocess.subprocess, { detached: true });\n } catch (error) {\n if (firstError === undefined) {\n firstError = error as Error;\n }\n }\n }\n\n if (firstError !== undefined) {\n // This is generally an unexpected error such as the TaskKill.exe command not being found,\n // not a trivial issue such as a nonexistent PID. Since this occurs during process shutdown,\n // we should not interfere with control flow by throwing an exception or calling process.exit().\n // So simply write to STDERR and ensure our exit code indicates the problem.\n // eslint-disable-next-line no-console\n console.error('\\nAn unexpected error was encountered while attempting to clean up child processes:');\n // eslint-disable-next-line no-console\n console.error(firstError.toString());\n if (!process.exitCode) {\n process.exitCode = 1;\n }\n }\n }\n }\n\n private static _validateSubprocessOptions(subprocessOptions: ISubprocessOptions): void {\n if (!SubprocessTerminator._isWindows) {\n if (!subprocessOptions.detached) {\n // Setting detached=true is what creates the process group that we use to kill the children\n throw new Error('killProcessTree() requires detached=true on this operating system');\n }\n }\n }\n\n private static _onExit(exitCode: number): void {\n SubprocessTerminator._logDebug(`received exit(${exitCode})`);\n\n SubprocessTerminator._cleanupChildProcesses();\n\n SubprocessTerminator._logDebug(`finished exit()`);\n }\n\n private static _onTerminateSignal(signal: string): void {\n SubprocessTerminator._logDebug(`received signal ${signal}`);\n\n SubprocessTerminator._cleanupChildProcesses();\n\n // When a listener is added to SIGTERM, Node.js strangely provides no way to reference\n // the original handler. But we can invoke it by removing our listener and then resending\n // the signal to our own process.\n SubprocessTerminator._logDebug(`relaying ${signal}`);\n process.kill(process.pid, signal);\n }\n\n // For debugging\n private static _logDebug(message: string): void {\n //const logLine: string = `SubprocessTerminator: [${process.pid}] ${message}`;\n // fs.writeFileSync('trace.log', logLine + '\\n', { flag: 'a' });\n //console.log(logLine);\n }\n}\n"]}