schangxiang@126.com
2025-09-18 49a51c068d62084bc4c3e77c4be94a20de556c4a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
'use strict';
 
const sleep = require('mz-modules/sleep');
const awaitEvent = require('await-event');
const pstree = require('ps-tree');
 
module.exports = function* (subProcess, timeout) {
  const pid = subProcess.process ? subProcess.process.pid : subProcess.pid;
  const childPids = yield getChildPids(pid);
  yield [
    killProcess(subProcess, timeout),
    killChildren(childPids, timeout),
  ];
};
 
// kill process, if SIGTERM not work, try SIGKILL
function* killProcess(subProcess, timeout) {
  subProcess.kill('SIGTERM');
  yield Promise.race([
    awaitEvent(subProcess, 'exit'),
    sleep(timeout),
  ]);
  if (subProcess.killed) return;
  // SIGKILL: http://man7.org/linux/man-pages/man7/signal.7.html
  // worker: https://github.com/nodejs/node/blob/master/lib/internal/cluster/worker.js#L22
  // subProcess.kill is wrapped to subProcess.destroy, it will wait to disconnected.
  (subProcess.process || subProcess).kill('SIGKILL');
}
 
// kill all children processes, if SIGTERM not work, try SIGKILL
function* killChildren(children, timeout) {
  if (!children.length) return;
  kill(children, 'SIGTERM');
 
  const start = Date.now();
  // if timeout is 1000, it will check twice.
  const checkInterval = 400;
  let unterminated = [];
 
  while (Date.now() - start < timeout - checkInterval) {
    yield sleep(checkInterval);
    unterminated = getUnterminatedProcesses(children);
    if (!unterminated.length) return;
  }
  kill(unterminated, 'SIGKILL');
}
 
function getChildPids(pid) {
  return new Promise(resolve => {
    pstree(pid, (err, children) => {
      // if get children error, just ignore it
      if (err) children = [];
      resolve(children.map(children => parseInt(children.PID)));
    });
  });
}
 
function kill(pids, signal) {
  for (const pid of pids) {
    try {
      process.kill(pid, signal);
    } catch (_) {
      // ignore
    }
  }
}
 
function getUnterminatedProcesses(pids) {
  return pids.filter(pid => {
    try {
      // success means it's still alive
      process.kill(pid, 0);
      return true;
    } catch (err) {
      // error means it's dead
      return false;
    }
  });
}