Skip to content

Commit 2178340

Browse files
authored
fix(core): kill child process tree in different running tasks (#33636)
## Current Behavior When Nx commands finish or receive termination signals (SIGINT, SIGTERM, SIGHUP), child processes spawned by continuous tasks (such as `nx serve`) can remain orphaned in certain scenarios. This happens because only the direct child process is killed using `childProcess.kill()`, leaving grandchild processes running. ## Expected Behavior When Nx terminates, all processes in the spawned process tree should be properly terminated and no orphaned processes should remain. ## Related Issue(s) Fixes #32438 Fixes #33460 ## Changes - Updated signal handlers in `RunningNodeProcess` to use `this.kill()` instead of `this.childProcess.kill()`, leveraging the existing `tree-kill` implementation - Added `tree-kill` to `NodeChildProcessWithNonDirectOutput` and `NodeChildProcessWithDirectOutput` kill methods to ensure entire process trees are terminated
1 parent c08e83c commit 2178340

2 files changed

Lines changed: 17 additions & 12 deletions

File tree

packages/nx/src/executors/run-commands/running-tasks.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -515,20 +515,20 @@ class RunningNodeProcess implements RunningTask {
515515
});
516516
// Terminate any task processes on exit
517517
process.on('exit', () => {
518-
this.childProcess.kill();
518+
this.kill();
519519
});
520520
process.on('SIGINT', () => {
521-
this.childProcess.kill('SIGTERM');
521+
this.kill('SIGTERM');
522522
// we exit here because we don't need to write anything to cache.
523523
process.exit(signalToCode('SIGINT'));
524524
});
525525
process.on('SIGTERM', () => {
526-
this.childProcess.kill('SIGTERM');
526+
this.kill('SIGTERM');
527527
// no exit here because we expect child processes to terminate which
528528
// will store results to the cache and will terminate this process
529529
});
530530
process.on('SIGHUP', () => {
531-
this.childProcess.kill('SIGTERM');
531+
this.kill('SIGTERM');
532532
// no exit here because we expect child processes to terminate which
533533
// will store results to the cache and will terminate this process
534534
});

packages/nx/src/tasks-runner/running-tasks/node-child-process.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { ChildProcess, Serializable } from 'child_process';
2-
import { signalToCode } from '../../utils/exit-codes';
3-
import { RunningTask } from './running-task';
4-
import { Transform } from 'stream';
51
import * as chalk from 'chalk';
2+
import type { ChildProcess, Serializable } from 'child_process';
63
import { readFileSync } from 'fs';
4+
import { Transform } from 'stream';
5+
import * as treeKill from 'tree-kill';
6+
import { signalToCode } from '../../utils/exit-codes';
7+
import type { RunningTask } from './running-task';
78

89
export class NodeChildProcessWithNonDirectOutput implements RunningTask {
910
private terminalOutput: string = '';
@@ -100,8 +101,10 @@ export class NodeChildProcessWithNonDirectOutput implements RunningTask {
100101
}
101102
}
102103
public kill(signal?: NodeJS.Signals) {
103-
if (this.childProcess.connected) {
104-
this.childProcess.kill(signal);
104+
if (this.childProcess?.pid) {
105+
treeKill(this.childProcess.pid, signal, () => {
106+
// Ignore errors - process may have already exited
107+
});
105108
}
106109
}
107110
}
@@ -223,8 +226,10 @@ export class NodeChildProcessWithDirectOutput implements RunningTask {
223226
}
224227

225228
kill(signal?: NodeJS.Signals): void {
226-
if (this.childProcess.connected) {
227-
this.childProcess.kill(signal);
229+
if (this.childProcess?.pid) {
230+
treeKill(this.childProcess.pid, signal, () => {
231+
// Ignore errors - process may have already exited
232+
});
228233
}
229234
}
230235
}

0 commit comments

Comments
 (0)