File size: 2,223 Bytes
cd8bd0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"use strict";

// Cross-platform "kill the whole process tree" helper (#3347).
//
// The embedded server is spawned via process.execPath (= omniroute.exe) with
// ELECTRON_RUN_AS_NODE=1, and it in turn spawns grandchildren (embedded services,
// MITM proxy, tunnels — several also omniroute.exe-as-node). On Windows, Node's
// ChildProcess.kill()/SIGTERM/SIGKILL only terminate the DIRECT child via
// TerminateProcess — they do NOT walk the tree. Surviving grandchildren keep a lock
// on omniroute.exe, so the process "hangs in memory" after Exit and updates fail with
// "file in use". Windows needs `taskkill /PID <pid> /T /F` (the /T flag terminates the
// process AND its descendants). POSIX keeps signal-based termination, which propagates.

const { spawn } = require("child_process");

/**
 * Terminate a child process and all of its descendants.
 * @param {{ pid?: number, kill?: (signal?: string) => void } | null | undefined} proc
 * @param {{ platform?: string, signal?: string, spawnFn?: typeof spawn }} [options]
 */
function killProcessTree(proc, options = {}) {
  if (!proc || proc.pid == null) return;
  const platform = options.platform || process.platform;
  const signal = options.signal || "SIGTERM";

  if (platform === "win32") {
    const spawnFn = options.spawnFn || spawn;
    try {
      // Array args + no shell → the pid (an integer we own) is never interpolated into a
      // shell command string (Hard Rule #13). /T walks the tree, /F forces termination.
      const killer = spawnFn("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
        windowsHide: true,
      });
      if (killer && typeof killer.on === "function") {
        killer.on("error", () => {
          try {
            proc.kill(signal);
          } catch {
            /* already dead */
          }
        });
      }
    } catch {
      // taskkill unavailable (rare) — fall back to the direct kill.
      try {
        proc.kill(signal);
      } catch {
        /* already dead */
      }
    }
    return;
  }

  // POSIX: signals propagate to the process group of a normally-spawned child.
  try {
    proc.kill(signal);
  } catch {
    /* already dead */
  }
}

module.exports = { killProcessTree };