diff --git a/package-lock.json b/package-lock.json index 7c5f0d45b..d01d66717 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16521,6 +16521,15 @@ "integrity": "sha512-+AqO1Ae+N/4r7Rvchrdm432afjT9hqJRyBN3DQv9At0tPz4hIFSGKbq64fN9dVoCow4oggIIax5/iONx0r9hZw==", "dev": true }, + "pstree.remy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz", + "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==", + "dev": true, + "requires": { + "ps-tree": "^1.1.0" + } + }, "public-encrypt": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", diff --git a/package.json b/package.json index 25f5b4d1e..f42f937d2 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "postcss-prepend-imports": "^1.0.1", "postcss-preset-env": "^5.2.1", "prettier": "^1.13.4", + "pstree.remy": "^1.1.0", "query-string": "^6.1.0", "raw-loader": "^0.5.1", "react": "^16.4.0", diff --git a/scripts/watcher/LongRunningExecutor.ts b/scripts/watcher/LongRunningExecutor.ts index 7019c1fb3..c1c1b4ff8 100644 --- a/scripts/watcher/LongRunningExecutor.ts +++ b/scripts/watcher/LongRunningExecutor.ts @@ -1,6 +1,8 @@ import { ChildProcess } from "child_process"; import spawn from "cross-spawn"; import { Cancelable, debounce } from "lodash"; +import psTree from "pstree.remy"; + import { Executor } from "./types"; interface LongRunningExecutorOptions { @@ -31,9 +33,6 @@ export default class LongRunningExecutor implements Executor { this.isRunning = true; this.process = spawn(this.cmd, this.args as string[], { stdio: "inherit", - // Have all child processes in their own group. - // See `process.kill` below. - detached: true, shell: !this.args, }); @@ -51,18 +50,43 @@ export default class LongRunningExecutor implements Executor { }); } - private restart(): void { + private async restart(): Promise { this.shouldRestart = true; - // Using the `-` will kill all child procceses in the group. - // See: https://azimi.me/2014/12/31/kill-child_process-node-js.html - process.kill(-this.process!.pid, "SIGTERM"); + return this.internalKill(); } - private kill(): void { + private async kill(): Promise { this.shouldRestart = false; - // Using the `-` will kill all child procceses in the group. - // See: https://azimi.me/2014/12/31/kill-child_process-node-js.html - process.kill(-this.process!.pid, "SIGTERM"); + return this.internalKill(); + } + + private async internalKill(): Promise { + return new Promise((resolve, reject) => { + const signal = "SIGTERM"; + if (process.platform === "win32") { + // Force kill (/F) the whole child tree (/T) by PID + spawn.sync("taskkill", [ + "/pid", + this.process!.pid.toString(), + "/T", + "/F", + ]); + resolve(); + return; + } + + psTree(this.process!.pid, (err, kids) => { + if (err) { + reject(err); + } + spawn.sync("kill", [ + `-${signal}`, + this.process!.pid.toString(), + ...kids.map(p => p.PID.toString()), + ]); + resolve(); + }); + }); } // This is called before watching starts. @@ -71,10 +95,10 @@ export default class LongRunningExecutor implements Executor { } // This is called before exiting. - public onCleanup() { + public async onCleanup() { this.restartDebounced.cancel(); if (this.isRunning) { - this.kill(); + await this.kill(); } } diff --git a/scripts/watcher/pstree.remy.d.ts b/scripts/watcher/pstree.remy.d.ts new file mode 100644 index 000000000..4cca7eff8 --- /dev/null +++ b/scripts/watcher/pstree.remy.d.ts @@ -0,0 +1,6 @@ +declare module "pstree.remy" { + export default function psTree( + pid: number, + callback: (err: Error, kids: Array<{ PID: number }>) => void + ): void; +} diff --git a/scripts/watcher/types.ts b/scripts/watcher/types.ts index 15bd6090b..89ed07a8d 100644 --- a/scripts/watcher/types.ts +++ b/scripts/watcher/types.ts @@ -13,7 +13,7 @@ export interface Watcher { export interface Executor { onInit?(): void; - onCleanup?(): void; + onCleanup?(): void | Promise; execute(filePath: string): void; } diff --git a/scripts/watcher/watch.ts b/scripts/watcher/watch.ts index 8a3feac75..5c1825e30 100644 --- a/scripts/watcher/watch.ts +++ b/scripts/watcher/watch.ts @@ -32,10 +32,10 @@ function prependRootDir(prepend: string, cfg: WatchConfig): WatchConfig { function setupCleanup(config: Config) { ["SIGINT", "SIGTERM"].forEach(signal => - process.once(signal as any, () => { + process.once(signal as any, async () => { for (const key of Object.keys(config.watchers)) { if (config.watchers[key].executor.onCleanup) { - config.watchers[key].executor.onCleanup!(); + await config.watchers[key].executor.onCleanup!(); } } process.exit(0);