-
-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathindex.js
201 lines (161 loc) · 5.12 KB
/
index.js
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import process from 'node:process';
import {taskkill} from 'taskkill';
import {execa} from 'execa';
import AggregateError from 'aggregate-error';
import {portToPid} from 'pid-port';
import {processExistsMultiple, filterExistingProcesses} from 'process-exists';
import psList from 'ps-list';
// If we check too soon, we're unlikely to see process killed so we essentially wait 3*ALIVE_CHECK_MIN_INTERVAL before the second check while producing unnecessary load.
// Primitive tests show that for a process which just dies on kill on a system without much load, we can usually see the process die in 5 ms.
// Checking once a second creates low enough load to not bother increasing maximum interval further, 1280 as first x to satisfy 2^^x * ALIVE_CHECK_MIN_INTERVAL > 1000.
const ALIVE_CHECK_MIN_INTERVAL = 5;
const ALIVE_CHECK_MAX_INTERVAL = 1280;
const TASKKILL_EXIT_CODE_FOR_PROCESS_FILTERING_SIGTERM = 255;
const delay = ms => new Promise(resolve => {
setTimeout(resolve, ms);
});
const missingBinaryError = async (command, arguments_) => {
try {
return await execa(command, arguments_);
} catch (error) {
if (error.code === 'ENOENT') {
const newError = new Error(`\`${command}\` doesn't seem to be installed and is required by fkill`);
newError.sourceError = error;
throw newError;
}
throw error;
}
};
const windowsKill = async (input, options) => {
try {
return await taskkill(input, {
force: options.force,
tree: options.tree === undefined ? true : options.tree,
});
} catch (error) {
if (error.exitCode === TASKKILL_EXIT_CODE_FOR_PROCESS_FILTERING_SIGTERM && !options.force) {
return;
}
throw error;
}
};
const macosKill = (input, options) => {
const killByName = typeof input === 'string';
const command = killByName ? 'pkill' : 'kill';
const arguments_ = [input];
if (killByName && options.ignoreCase) {
arguments_.unshift('-i');
}
if (killByName) {
arguments_.unshift('-x');
}
// Must be last.
if (options.force) {
if (killByName) {
arguments_.unshift('-KILL');
} else {
arguments_.unshift('-9');
}
}
return missingBinaryError(command, arguments_);
};
const defaultKill = (input, options) => {
const killByName = typeof input === 'string';
const command = killByName ? 'killall' : 'kill';
const arguments_ = [input];
if (options.force) {
arguments_.unshift('-9');
}
if (killByName && options.ignoreCase) {
arguments_.unshift('-I');
}
return missingBinaryError(command, arguments_);
};
const kill = (() => {
if (process.platform === 'darwin') {
return macosKill;
}
if (process.platform === 'win32') {
return windowsKill;
}
return defaultKill;
})();
const parseInput = async input => {
if (typeof input === 'string' && input[0] === ':') {
return portToPid(Number.parseInt(input.slice(1), 10));
}
return input;
};
const getCurrentProcessParentsPID = processes => {
const processMap = new Map(processes.map(ps => [ps.pid, ps.ppid]));
const pids = [];
let currentId = process.pid;
while (currentId) {
pids.push(currentId);
currentId = processMap.get(currentId);
}
return pids;
};
const killWithLimits = async (input, options) => {
input = await parseInput(input);
if (input === process.pid) {
return;
}
if (input === 'node' || input === 'node.exe') {
const processes = await psList();
const pids = getCurrentProcessParentsPID(processes);
await Promise.all(processes.map(async ps => {
if ((ps.name === 'node' || ps.name === 'node.exe') && !pids.includes(ps.pid)) {
await kill(ps.pid, options);
}
}));
return;
}
await kill(input, options);
};
export default async function fkill(inputs, options = {}) {
inputs = [inputs].flat();
const exists = await processExistsMultiple(inputs);
const errors = [];
const handleKill = async input => {
try {
await killWithLimits(input, options);
} catch (error) {
if (!exists.get(input)) {
errors.push(`Killing process ${input} failed: Process doesn't exist`);
return;
}
errors.push(`Killing process ${input} failed: ${error.message.replace(/.*\n/, '').replace(/kill: \d+: /, '').trim()}`);
}
};
await Promise.all(inputs.map(input => handleKill(input)));
if (errors.length > 0 && !options.silent) {
throw new AggregateError(errors);
}
if (options.forceAfterTimeout !== undefined && !options.force) {
const endTime = Date.now() + options.forceAfterTimeout;
let interval = ALIVE_CHECK_MIN_INTERVAL;
if (interval > options.forceAfterTimeout) {
interval = options.forceAfterTimeout;
}
let alive = inputs;
do {
await delay(interval); // eslint-disable-line no-await-in-loop
alive = await filterExistingProcesses(alive); // eslint-disable-line no-await-in-loop
interval *= 2;
if (interval > ALIVE_CHECK_MAX_INTERVAL) {
interval = ALIVE_CHECK_MAX_INTERVAL;
}
} while (Date.now() < endTime && alive.length > 0);
if (alive.length > 0) {
await Promise.all(alive.map(async input => {
try {
await killWithLimits(input, {...options, force: true});
} catch {
// It's hard to filter does-not-exist kind of errors, so we ignore all of them here.
// All meaningful errors should have been thrown before this operation takes place.
}
}));
}
}
}