@gtkx/utils 1.6.0 → 2.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -44
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/path.d.ts +5 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +10 -0
- package/dist/path.js.map +1 -0
- package/dist/process/index.d.ts +3 -1
- package/dist/process/index.d.ts.map +1 -1
- package/dist/process/index.js +3 -1
- package/dist/process/index.js.map +1 -1
- package/dist/process/kill-marked-processes.d.ts +3 -2
- package/dist/process/kill-marked-processes.d.ts.map +1 -1
- package/dist/process/kill-marked-processes.js +39 -13
- package/dist/process/kill-marked-processes.js.map +1 -1
- package/dist/process/kill-process-group.d.ts +16 -0
- package/dist/process/kill-process-group.d.ts.map +1 -0
- package/dist/process/kill-process-group.js +70 -0
- package/dist/process/kill-process-group.js.map +1 -0
- package/dist/process/process-guard.js +192 -6
- package/dist/process/process-guard.js.map +1 -1
- package/dist/process/process-status.d.ts +5 -0
- package/dist/process/process-status.d.ts.map +1 -0
- package/dist/process/process-status.js +19 -0
- package/dist/process/process-status.js.map +1 -0
- package/dist/process/resolve-executable.d.ts +2 -2
- package/dist/process/resolve-executable.d.ts.map +1 -1
- package/dist/process/resolve-executable.js +3 -37
- package/dist/process/resolve-executable.js.map +1 -1
- package/dist/process/spawn-with-parent-death-signal.d.ts +7 -1
- package/dist/process/spawn-with-parent-death-signal.d.ts.map +1 -1
- package/dist/process/spawn-with-parent-death-signal.js +315 -8
- package/dist/process/spawn-with-parent-death-signal.js.map +1 -1
- package/package.json +12 -4
- package/src/index.ts +1 -1
- package/src/path.ts +17 -0
- package/src/process/index.ts +14 -1
- package/src/process/kill-marked-processes.ts +58 -13
- package/src/process/kill-process-group.ts +105 -0
- package/src/process/process-guard.ts +261 -6
- package/src/process/process-status.ts +25 -0
- package/src/process/resolve-executable.ts +4 -46
- package/src/process/spawn-with-parent-death-signal.ts +422 -11
- package/dist/map/get-or-insert.d.ts +0 -8
- package/dist/map/get-or-insert.d.ts.map +0 -1
- package/dist/map/get-or-insert.js +0 -10
- package/dist/map/get-or-insert.js.map +0 -1
- package/dist/map/index.d.ts +0 -2
- package/dist/map/index.d.ts.map +0 -1
- package/dist/map/index.js +0 -2
- package/dist/map/index.js.map +0 -1
- package/src/map/get-or-insert.ts +0 -18
- package/src/map/index.ts +0 -1
|
@@ -1,15 +1,168 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
4
|
import { Socket } from "node:net";
|
|
4
5
|
import { dirname, extname, join } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { warn } from "../log/default-logger.js";
|
|
6
8
|
import { killMarkedProcesses, PROCESS_MARKER } from "./kill-marked-processes.js";
|
|
9
|
+
import { cleanupDirectoryIdentity, killProcessGroup, processGroupIdentity, removeCleanupDirectory, } from "./kill-process-group.js";
|
|
10
|
+
import { isReapedState, readProcessStatFields } from "./process-status.js";
|
|
7
11
|
import { resolveExecutable } from "./resolve-executable.js";
|
|
8
12
|
const MODULE_PATH = fileURLToPath(import.meta.url);
|
|
9
13
|
const GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);
|
|
10
14
|
const RUN_ID = randomBytes(8).toString("hex");
|
|
11
15
|
const RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;
|
|
12
|
-
const guard = {};
|
|
16
|
+
const guard = { jobs: new Map() };
|
|
17
|
+
const SUPERVISOR_NAME = "gtkx-process-supervisor";
|
|
18
|
+
const SUPERVISOR_CLEANUP_NAME = "gtkx-process-cleanup";
|
|
19
|
+
const SUPERVISOR_WATCH_NAME = "gtkx-process-watch";
|
|
20
|
+
const SUPERVISOR_KILL_GROUP_BODY = [
|
|
21
|
+
"remaining=40",
|
|
22
|
+
'while [ "$remaining" -gt 0 ] && kill -KILL "-$group" 2>/dev/null; do',
|
|
23
|
+
' "$sleep_command" 0.025',
|
|
24
|
+
" remaining=$((remaining - 1))",
|
|
25
|
+
"done",
|
|
26
|
+
];
|
|
27
|
+
const SUPERVISOR_REMOVE_RUNTIME_BODY = [
|
|
28
|
+
"remaining=40",
|
|
29
|
+
'while [ "$remaining" -gt 0 ] && ' +
|
|
30
|
+
'[ "$("$stat_command" -c "%d:%i:%u" -- "$runtime" 2>/dev/null)" = "$identity" ]; do',
|
|
31
|
+
' "$rm_command" -rf -- "$runtime"',
|
|
32
|
+
' "$sleep_command" 0.025',
|
|
33
|
+
" remaining=$((remaining - 1))",
|
|
34
|
+
"done",
|
|
35
|
+
];
|
|
36
|
+
const SUPERVISOR_CLEANUP_BODY = [...SUPERVISOR_KILL_GROUP_BODY, ...SUPERVISOR_REMOVE_RUNTIME_BODY];
|
|
37
|
+
const SUPERVISOR_CLEANUP_SCRIPT = [
|
|
38
|
+
"runtime=$1",
|
|
39
|
+
"identity=$2",
|
|
40
|
+
"group=$3",
|
|
41
|
+
"stat_command=$4",
|
|
42
|
+
"rm_command=$5",
|
|
43
|
+
"sleep_command=$6",
|
|
44
|
+
...SUPERVISOR_CLEANUP_BODY,
|
|
45
|
+
].join("\n");
|
|
46
|
+
const SUPERVISOR_WATCH_SCRIPT = [
|
|
47
|
+
"runtime=$1",
|
|
48
|
+
"identity=$2",
|
|
49
|
+
"group=$3",
|
|
50
|
+
"group_start=$4",
|
|
51
|
+
"expected_parent=$5",
|
|
52
|
+
"expected_parent_start=$6",
|
|
53
|
+
"stat_command=$7",
|
|
54
|
+
"rm_command=$8",
|
|
55
|
+
"sleep_command=$9",
|
|
56
|
+
"matches_process() {",
|
|
57
|
+
" expected_process=$1",
|
|
58
|
+
" expected_start=$2",
|
|
59
|
+
" process_stat=",
|
|
60
|
+
' IFS= read -r process_stat < "/proc/$expected_process/stat" || return 1',
|
|
61
|
+
" process_tail=${process_stat##*) }",
|
|
62
|
+
" set -- $process_tail",
|
|
63
|
+
' case "$1" in Z|X|x) return 1 ;; esac',
|
|
64
|
+
' [ "${20}" = "$expected_start" ]',
|
|
65
|
+
"}",
|
|
66
|
+
"matches_parent() {",
|
|
67
|
+
' matches_process "$expected_parent" "$expected_parent_start"',
|
|
68
|
+
"}",
|
|
69
|
+
"kill_group() {",
|
|
70
|
+
...SUPERVISOR_KILL_GROUP_BODY.map((line) => ` ${line}`),
|
|
71
|
+
"}",
|
|
72
|
+
"cleanup() {",
|
|
73
|
+
' trap "" TERM INT HUP',
|
|
74
|
+
" kill_group",
|
|
75
|
+
...SUPERVISOR_REMOVE_RUNTIME_BODY.map((line) => ` ${line}`),
|
|
76
|
+
" exit 0",
|
|
77
|
+
"}",
|
|
78
|
+
"trap cleanup TERM INT HUP",
|
|
79
|
+
'while matches_process "$group" "$group_start" && matches_parent; do',
|
|
80
|
+
' "$sleep_command" 0.1',
|
|
81
|
+
"done",
|
|
82
|
+
"kill_group",
|
|
83
|
+
"while matches_parent; do",
|
|
84
|
+
' "$sleep_command" 0.1',
|
|
85
|
+
"done",
|
|
86
|
+
"cleanup",
|
|
87
|
+
].join("\n");
|
|
88
|
+
const SUPERVISOR_SCRIPT = [
|
|
89
|
+
"runtime=$1",
|
|
90
|
+
"identity=$2",
|
|
91
|
+
"expected_parent=$3",
|
|
92
|
+
"expected_parent_start=$4",
|
|
93
|
+
"stat_command=$5",
|
|
94
|
+
"rm_command=$6",
|
|
95
|
+
"shell_command=$7",
|
|
96
|
+
"setsid_command=$8",
|
|
97
|
+
"sleep_command=$9",
|
|
98
|
+
"shift 9",
|
|
99
|
+
"watch_name=$1",
|
|
100
|
+
"watch_script=$2",
|
|
101
|
+
"cleanup_name=$3",
|
|
102
|
+
"cleanup_script=$4",
|
|
103
|
+
"shift 4",
|
|
104
|
+
"process_start() {",
|
|
105
|
+
" process_stat=",
|
|
106
|
+
' IFS= read -r process_stat < "/proc/$1/stat" || return 1',
|
|
107
|
+
" process_tail=${process_stat##*) }",
|
|
108
|
+
" set -- $process_tail",
|
|
109
|
+
' case "$1" in Z|X|x) return 1 ;; esac',
|
|
110
|
+
' printf "%s" "${20}"',
|
|
111
|
+
"}",
|
|
112
|
+
"matches_parent() {",
|
|
113
|
+
" parent_stat=",
|
|
114
|
+
' IFS= read -r parent_stat < "/proc/$expected_parent/stat" || return 1',
|
|
115
|
+
" parent_tail=${parent_stat##*) }",
|
|
116
|
+
" set -- $parent_tail",
|
|
117
|
+
' case "$1" in Z|X|x) return 1 ;; esac',
|
|
118
|
+
' [ "${20}" = "$expected_parent_start" ]',
|
|
119
|
+
"}",
|
|
120
|
+
"terminate() {",
|
|
121
|
+
' trap "" CONT TERM INT HUP',
|
|
122
|
+
" while :; do",
|
|
123
|
+
' "$setsid_command" "$shell_command" -c "$cleanup_script" "$cleanup_name" "$runtime" "$identity" "$$" ' +
|
|
124
|
+
'"$stat_command" "$rm_command" "$sleep_command" </dev/null >/dev/null 2>&1 &',
|
|
125
|
+
" cleanup=$!",
|
|
126
|
+
' wait "$cleanup" 2>/dev/null',
|
|
127
|
+
' "$sleep_command" 0.025',
|
|
128
|
+
" done",
|
|
129
|
+
"}",
|
|
130
|
+
"continue_or_terminate() {",
|
|
131
|
+
" remaining=40",
|
|
132
|
+
' while matches_parent && [ "$remaining" -gt 0 ]; do',
|
|
133
|
+
' "$sleep_command" 0.025',
|
|
134
|
+
" remaining=$((remaining - 1))",
|
|
135
|
+
" done",
|
|
136
|
+
" if ! matches_parent; then terminate; fi",
|
|
137
|
+
"}",
|
|
138
|
+
"trap continue_or_terminate CONT",
|
|
139
|
+
"trap terminate TERM INT HUP",
|
|
140
|
+
"supervisor_start=$(process_start \"$$\") || terminate",
|
|
141
|
+
'( unset GTKX_PROCESS_GUARD; exec "$setsid_command" "$shell_command" -c "$watch_script" "$watch_name" "$runtime" ' +
|
|
142
|
+
'"$identity" "$$" "$supervisor_start" "$expected_parent" "$expected_parent_start" "$stat_command" ' +
|
|
143
|
+
'"$rm_command" "$sleep_command" ) </dev/null >/dev/null 2>&1 &',
|
|
144
|
+
"child=",
|
|
145
|
+
"if ! matches_parent; then terminate; fi",
|
|
146
|
+
'"$@" &',
|
|
147
|
+
"child=$!",
|
|
148
|
+
"while :; do",
|
|
149
|
+
' wait "$child"',
|
|
150
|
+
" status=$?",
|
|
151
|
+
' if kill -0 "$child" 2>/dev/null; then continue; fi',
|
|
152
|
+
' exit "$status"',
|
|
153
|
+
"done",
|
|
154
|
+
].join("\n");
|
|
155
|
+
const processIdentity = (pid) => {
|
|
156
|
+
const fields = readProcessStatFields(pid);
|
|
157
|
+
const startTime = fields?.[19];
|
|
158
|
+
if (startTime === undefined || isReapedState(fields?.[0])) {
|
|
159
|
+
throw new Error(`Failed to identify process ${String(pid)}`);
|
|
160
|
+
}
|
|
161
|
+
return { pid, startTime };
|
|
162
|
+
};
|
|
163
|
+
const writeGuardCommand = (child, operation, job) => {
|
|
164
|
+
child.stdin?.write(`${operation}${JSON.stringify(job)}\n`);
|
|
165
|
+
};
|
|
13
166
|
const releaseGuard = (child) => {
|
|
14
167
|
child.unref();
|
|
15
168
|
const { stdin } = child;
|
|
@@ -17,34 +170,188 @@ const releaseGuard = (child) => {
|
|
|
17
170
|
stdin.unref();
|
|
18
171
|
}
|
|
19
172
|
};
|
|
20
|
-
const
|
|
173
|
+
const isSameProcessWatch = (left, right) => left.owner.pid === right.owner.pid &&
|
|
174
|
+
left.owner.startTime === right.owner.startTime &&
|
|
175
|
+
left.target.pid === right.target.pid &&
|
|
176
|
+
left.target.startTime === right.target.startTime;
|
|
177
|
+
const configureProcessWatch = (watch) => {
|
|
178
|
+
if (watch === undefined) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (guard.watch !== undefined && !isSameProcessWatch(guard.watch, watch)) {
|
|
182
|
+
throw new Error("The process guard is already watching another owner");
|
|
183
|
+
}
|
|
184
|
+
if (guard.child !== undefined && guard.watch === undefined) {
|
|
185
|
+
throw new Error("The process guard was started before its owner was registered");
|
|
186
|
+
}
|
|
187
|
+
guard.watch = watch;
|
|
188
|
+
};
|
|
189
|
+
const guardArguments = () => guard.watch === undefined
|
|
190
|
+
? [GUARD_PATH, RUN_PREFIX]
|
|
191
|
+
: [GUARD_PATH, RUN_PREFIX, JSON.stringify(guard.watch)];
|
|
192
|
+
const startGuard = (watch) => {
|
|
193
|
+
configureProcessWatch(watch);
|
|
21
194
|
if (guard.child) {
|
|
22
195
|
return;
|
|
23
196
|
}
|
|
24
|
-
const child = spawn(process.execPath,
|
|
197
|
+
const child = spawn(process.execPath, guardArguments(), {
|
|
25
198
|
detached: true,
|
|
26
199
|
stdio: ["pipe", "ignore", "ignore"],
|
|
27
200
|
});
|
|
28
201
|
child.on("error", () => {
|
|
29
|
-
guard.child
|
|
202
|
+
if (guard.child === child) {
|
|
203
|
+
guard.child = undefined;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
child.on("exit", () => {
|
|
207
|
+
if (guard.child === child) {
|
|
208
|
+
guard.child = undefined;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
child.stdin.on("error", () => {
|
|
212
|
+
if (guard.child === child) {
|
|
213
|
+
guard.child = undefined;
|
|
214
|
+
}
|
|
30
215
|
});
|
|
31
216
|
guard.child = child;
|
|
217
|
+
for (const job of guard.jobs.values()) {
|
|
218
|
+
writeGuardCommand(child, "+", job);
|
|
219
|
+
}
|
|
32
220
|
releaseGuard(child);
|
|
33
221
|
};
|
|
34
|
-
|
|
222
|
+
const watchParentProcess = () => {
|
|
223
|
+
const parentId = process.ppid;
|
|
224
|
+
const watch = {
|
|
225
|
+
owner: processIdentity(parentId),
|
|
226
|
+
target: processIdentity(process.pid),
|
|
227
|
+
};
|
|
228
|
+
if (process.ppid !== parentId) {
|
|
229
|
+
throw new Error("The parent process exited while its guard was starting");
|
|
230
|
+
}
|
|
231
|
+
startGuard(watch);
|
|
232
|
+
};
|
|
233
|
+
const registerJob = (job) => {
|
|
234
|
+
guard.jobs.set(job.marker, job);
|
|
235
|
+
if (guard.child) {
|
|
236
|
+
writeGuardCommand(guard.child, "+", job);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const unregisterJob = (job) => {
|
|
240
|
+
guard.jobs.delete(job.marker);
|
|
241
|
+
if (guard.child) {
|
|
242
|
+
writeGuardCommand(guard.child, "-", job);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
const rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] }) => {
|
|
246
|
+
if (processGroup === undefined) {
|
|
247
|
+
child.kill("SIGKILL");
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
killProcessGroup(processGroup);
|
|
251
|
+
}
|
|
252
|
+
killMarkedProcesses(marker);
|
|
253
|
+
for (const identity of cleanupDirectories) {
|
|
254
|
+
removeCleanupDirectory(identity);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
const captureCleanupDirectories = (command, paths) => {
|
|
258
|
+
const identities = paths.map((path) => cleanupDirectoryIdentity(path));
|
|
259
|
+
if (identities.includes(undefined)) {
|
|
260
|
+
throw new Error(`Failed to identify a cleanup directory for ${command}`);
|
|
261
|
+
}
|
|
262
|
+
return identities.filter((identity) => identity !== undefined);
|
|
263
|
+
};
|
|
264
|
+
const readResource = (path) => {
|
|
265
|
+
try {
|
|
266
|
+
return readFileSync(path, "utf8").trim();
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return "unknown";
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const openDescriptors = () => {
|
|
273
|
+
try {
|
|
274
|
+
return String(readdirSync("/proc/self/fd").length);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return "unknown";
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const resourceSummary = () => `open descriptors ${openDescriptors()}, ` +
|
|
281
|
+
`cgroup pids ${readResource("/sys/fs/cgroup/pids.current")}/${readResource("/sys/fs/cgroup/pids.max")}`;
|
|
282
|
+
const spawnedProcessGroup = (child, command) => {
|
|
283
|
+
const processGroupId = child.pid;
|
|
284
|
+
if (processGroupId !== undefined) {
|
|
285
|
+
return processGroupIdentity(processGroupId);
|
|
286
|
+
}
|
|
287
|
+
child.on("error", (cause) => {
|
|
288
|
+
warn(`Cannot spawn ${command}: ${cause.code ?? cause.message}`);
|
|
289
|
+
});
|
|
290
|
+
return undefined;
|
|
291
|
+
};
|
|
292
|
+
const spawnGuarded = (command, options, signal, launchArguments) => {
|
|
35
293
|
const executable = resolveExecutable(command);
|
|
36
294
|
const setpriv = resolveExecutable("setpriv");
|
|
37
295
|
const jobValue = `${RUN_ID}/${randomBytes(4).toString("hex")}`;
|
|
296
|
+
const marker = `${PROCESS_MARKER}=${jobValue}`;
|
|
297
|
+
const cleanupDirectories = captureCleanupDirectories(command, options.cleanupDirectories ?? []);
|
|
38
298
|
startGuard();
|
|
39
|
-
const child = spawn(setpriv,
|
|
299
|
+
const child = spawn(setpriv, launchArguments(executable, cleanupDirectories), {
|
|
40
300
|
detached: true,
|
|
41
301
|
stdio: options.stdio ?? "ignore",
|
|
42
302
|
env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
|
|
43
303
|
});
|
|
304
|
+
const group = spawnedProcessGroup(child, command);
|
|
305
|
+
if (group === undefined) {
|
|
306
|
+
rollbackSpawn({ child, marker, cleanupDirectories });
|
|
307
|
+
throw new Error(`Failed to spawn ${command} or identify its process group (${resourceSummary()})`);
|
|
308
|
+
}
|
|
309
|
+
const job = { marker, processGroup: group, cleanupDirectories, signal };
|
|
310
|
+
registerJob(job);
|
|
44
311
|
child.on("exit", () => {
|
|
45
|
-
killMarkedProcesses(
|
|
312
|
+
killMarkedProcesses(job.marker);
|
|
313
|
+
unregisterJob(job);
|
|
46
314
|
});
|
|
47
315
|
return child;
|
|
316
|
+
};
|
|
317
|
+
function spawnWithParentDeathSignal(command, args, options = {}) {
|
|
318
|
+
return spawnGuarded(command, options, "SIGKILL", (executable) => ["--pdeathsig", "SIGKILL", executable, ...args]);
|
|
48
319
|
}
|
|
49
|
-
|
|
320
|
+
const spawnWithParentDeathSupervisor = (command, args, options) => {
|
|
321
|
+
const shell = resolveExecutable("sh");
|
|
322
|
+
const stat = resolveExecutable("stat");
|
|
323
|
+
const rm = resolveExecutable("rm");
|
|
324
|
+
const setsid = resolveExecutable("setsid");
|
|
325
|
+
const sleep = resolveExecutable("sleep");
|
|
326
|
+
return spawnGuarded(command, { ...options, cleanupDirectories: [options.cleanupDirectory] }, "SIGCONT", (executable, cleanupDirectories) => {
|
|
327
|
+
const identity = cleanupDirectories[0];
|
|
328
|
+
if (identity === undefined) {
|
|
329
|
+
throw new Error(`Failed to identify a cleanup directory for ${command}`);
|
|
330
|
+
}
|
|
331
|
+
return [
|
|
332
|
+
"--pdeathsig",
|
|
333
|
+
"SIGCONT",
|
|
334
|
+
shell,
|
|
335
|
+
"-c",
|
|
336
|
+
SUPERVISOR_SCRIPT,
|
|
337
|
+
SUPERVISOR_NAME,
|
|
338
|
+
identity.path,
|
|
339
|
+
`${identity.device}:${identity.inode}:${identity.userId}`,
|
|
340
|
+
String(process.pid),
|
|
341
|
+
processIdentity(process.pid).startTime,
|
|
342
|
+
stat,
|
|
343
|
+
rm,
|
|
344
|
+
shell,
|
|
345
|
+
setsid,
|
|
346
|
+
sleep,
|
|
347
|
+
SUPERVISOR_WATCH_NAME,
|
|
348
|
+
SUPERVISOR_WATCH_SCRIPT,
|
|
349
|
+
SUPERVISOR_CLEANUP_NAME,
|
|
350
|
+
SUPERVISOR_CLEANUP_SCRIPT,
|
|
351
|
+
executable,
|
|
352
|
+
...args,
|
|
353
|
+
];
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor, watchParentProcess };
|
|
50
357
|
//# sourceMappingURL=spawn-with-parent-death-signal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-with-parent-death-signal.js","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn, type StdioOptions } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { Socket } from \"node:net\";\nimport { dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { killMarkedProcesses, PROCESS_MARKER } from \"./kill-marked-processes.ts\";\nimport { resolveExecutable } from \"./resolve-executable.ts\";\n\ntype ParentDeathSpawnOptions = {\n stdio?: StdioOptions;\n env?: NodeJS.ProcessEnv;\n};\n\ntype GuardState = {\n child?: ChildProcess | undefined;\n};\n\nconst MODULE_PATH = fileURLToPath(import.meta.url);\nconst GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);\nconst RUN_ID = randomBytes(8).toString(\"hex\");\nconst RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;\nconst guard: GuardState = {};\n\nconst releaseGuard = (child: ChildProcess): void => {\n child.unref();\n const { stdin } = child;\n\n if (stdin instanceof Socket) {\n stdin.unref();\n }\n};\n\nconst startGuard = (): void => {\n if (guard.child) {\n return;\n }\n\n const child = spawn(process.execPath, [GUARD_PATH, RUN_PREFIX], {\n detached: true,\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n\n child.on(\"error\", () => {\n guard.child = undefined;\n });\n\n guard.child = child;\n releaseGuard(child);\n};\n\nfunction spawnWithParentDeathSignal(\n command: string,\n args: string[],\n options: ParentDeathSpawnOptions = {},\n): ChildProcess {\n const executable = resolveExecutable(command);\n const setpriv = resolveExecutable(\"setpriv\");\n const jobValue = `${RUN_ID}/${randomBytes(4).toString(\"hex\")}`;\n startGuard();\n\n const child = spawn(setpriv, [\"--pdeathsig\", \"SIGKILL\", executable, ...args], {\n detached: true,\n stdio: options.stdio ?? \"ignore\",\n env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },\n });\n\n child.on(\"exit\", () => {\n killMarkedProcesses(`${PROCESS_MARKER}=${jobValue}`);\n });\n\n return child;\n}\n\nexport { spawnWithParentDeathSignal };\n"]}
|
|
1
|
+
{"version":3,"file":"spawn-with-parent-death-signal.js","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn, type StdioOptions } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport { readdirSync, readFileSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { warn } from \"../log/default-logger.ts\";\nimport { killMarkedProcesses, PROCESS_MARKER } from \"./kill-marked-processes.ts\";\nimport {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.ts\";\nimport { isReapedState, readProcessStatFields } from \"./process-status.ts\";\nimport { resolveExecutable } from \"./resolve-executable.ts\";\n\ntype ParentDeathSpawnOptions = {\n stdio?: StdioOptions;\n env?: NodeJS.ProcessEnv;\n cleanupDirectories?: string[];\n};\n\ntype ParentDeathSupervisorOptions = Omit<ParentDeathSpawnOptions, \"cleanupDirectories\"> & {\n cleanupDirectory: string;\n};\n\ntype GuardSignal = \"SIGKILL\" | \"SIGCONT\";\n\ntype ProcessIdentity = {\n pid: number;\n startTime: string;\n};\n\ntype ProcessWatch = {\n owner: ProcessIdentity;\n target: ProcessIdentity;\n};\n\ntype LaunchArguments = (executable: string, cleanupDirectories: CleanupDirectoryIdentity[]) => string[];\n\ntype GuardJob = {\n marker: string;\n processGroup: ProcessGroupIdentity;\n cleanupDirectories: CleanupDirectoryIdentity[];\n signal: GuardSignal;\n};\n\ntype GuardState = {\n child?: ChildProcess | undefined;\n jobs: Map<string, GuardJob>;\n watch?: ProcessWatch;\n};\n\ntype SpawnRollback = {\n child: ChildProcess;\n marker: string;\n processGroup?: ProcessGroupIdentity;\n cleanupDirectories?: CleanupDirectoryIdentity[];\n};\n\nconst MODULE_PATH = fileURLToPath(import.meta.url);\nconst GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PATH)}`);\nconst RUN_ID = randomBytes(8).toString(\"hex\");\nconst RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;\nconst guard: GuardState = { jobs: new Map() };\nconst SUPERVISOR_NAME = \"gtkx-process-supervisor\";\nconst SUPERVISOR_CLEANUP_NAME = \"gtkx-process-cleanup\";\nconst SUPERVISOR_WATCH_NAME = \"gtkx-process-watch\";\nconst SUPERVISOR_KILL_GROUP_BODY = [\n \"remaining=40\",\n 'while [ \"$remaining\" -gt 0 ] && kill -KILL \"-$group\" 2>/dev/null; do',\n ' \"$sleep_command\" 0.025',\n \" remaining=$((remaining - 1))\",\n \"done\",\n];\nconst SUPERVISOR_REMOVE_RUNTIME_BODY = [\n \"remaining=40\",\n 'while [ \"$remaining\" -gt 0 ] && ' +\n '[ \"$(\"$stat_command\" -c \"%d:%i:%u\" -- \"$runtime\" 2>/dev/null)\" = \"$identity\" ]; do',\n ' \"$rm_command\" -rf -- \"$runtime\"',\n ' \"$sleep_command\" 0.025',\n \" remaining=$((remaining - 1))\",\n \"done\",\n];\nconst SUPERVISOR_CLEANUP_BODY = [...SUPERVISOR_KILL_GROUP_BODY, ...SUPERVISOR_REMOVE_RUNTIME_BODY];\nconst SUPERVISOR_CLEANUP_SCRIPT = [\n \"runtime=$1\",\n \"identity=$2\",\n \"group=$3\",\n \"stat_command=$4\",\n \"rm_command=$5\",\n \"sleep_command=$6\",\n ...SUPERVISOR_CLEANUP_BODY,\n].join(\"\\n\");\nconst SUPERVISOR_WATCH_SCRIPT = [\n \"runtime=$1\",\n \"identity=$2\",\n \"group=$3\",\n \"group_start=$4\",\n \"expected_parent=$5\",\n \"expected_parent_start=$6\",\n \"stat_command=$7\",\n \"rm_command=$8\",\n \"sleep_command=$9\",\n \"matches_process() {\",\n \" expected_process=$1\",\n \" expected_start=$2\",\n \" process_stat=\",\n ' IFS= read -r process_stat < \"/proc/$expected_process/stat\" || return 1',\n \" process_tail=${process_stat##*) }\",\n \" set -- $process_tail\",\n ' case \"$1\" in Z|X|x) return 1 ;; esac',\n ' [ \"${20}\" = \"$expected_start\" ]',\n \"}\",\n \"matches_parent() {\",\n ' matches_process \"$expected_parent\" \"$expected_parent_start\"',\n \"}\",\n \"kill_group() {\",\n ...SUPERVISOR_KILL_GROUP_BODY.map((line) => ` ${line}`),\n \"}\",\n \"cleanup() {\",\n ' trap \"\" TERM INT HUP',\n \" kill_group\",\n ...SUPERVISOR_REMOVE_RUNTIME_BODY.map((line) => ` ${line}`),\n \" exit 0\",\n \"}\",\n \"trap cleanup TERM INT HUP\",\n 'while matches_process \"$group\" \"$group_start\" && matches_parent; do',\n ' \"$sleep_command\" 0.1',\n \"done\",\n \"kill_group\",\n \"while matches_parent; do\",\n ' \"$sleep_command\" 0.1',\n \"done\",\n \"cleanup\",\n].join(\"\\n\");\nconst SUPERVISOR_SCRIPT = [\n \"runtime=$1\",\n \"identity=$2\",\n \"expected_parent=$3\",\n \"expected_parent_start=$4\",\n \"stat_command=$5\",\n \"rm_command=$6\",\n \"shell_command=$7\",\n \"setsid_command=$8\",\n \"sleep_command=$9\",\n \"shift 9\",\n \"watch_name=$1\",\n \"watch_script=$2\",\n \"cleanup_name=$3\",\n \"cleanup_script=$4\",\n \"shift 4\",\n \"process_start() {\",\n \" process_stat=\",\n ' IFS= read -r process_stat < \"/proc/$1/stat\" || return 1',\n \" process_tail=${process_stat##*) }\",\n \" set -- $process_tail\",\n ' case \"$1\" in Z|X|x) return 1 ;; esac',\n ' printf \"%s\" \"${20}\"',\n \"}\",\n \"matches_parent() {\",\n \" parent_stat=\",\n ' IFS= read -r parent_stat < \"/proc/$expected_parent/stat\" || return 1',\n \" parent_tail=${parent_stat##*) }\",\n \" set -- $parent_tail\",\n ' case \"$1\" in Z|X|x) return 1 ;; esac',\n ' [ \"${20}\" = \"$expected_parent_start\" ]',\n \"}\",\n \"terminate() {\",\n ' trap \"\" CONT TERM INT HUP',\n \" while :; do\",\n ' \"$setsid_command\" \"$shell_command\" -c \"$cleanup_script\" \"$cleanup_name\" \"$runtime\" \"$identity\" \"$$\" ' +\n '\"$stat_command\" \"$rm_command\" \"$sleep_command\" </dev/null >/dev/null 2>&1 &',\n \" cleanup=$!\",\n ' wait \"$cleanup\" 2>/dev/null',\n ' \"$sleep_command\" 0.025',\n \" done\",\n \"}\",\n \"continue_or_terminate() {\",\n \" remaining=40\",\n ' while matches_parent && [ \"$remaining\" -gt 0 ]; do',\n ' \"$sleep_command\" 0.025',\n \" remaining=$((remaining - 1))\",\n \" done\",\n \" if ! matches_parent; then terminate; fi\",\n \"}\",\n \"trap continue_or_terminate CONT\",\n \"trap terminate TERM INT HUP\",\n \"supervisor_start=$(process_start \\\"$$\\\") || terminate\",\n '( unset GTKX_PROCESS_GUARD; exec \"$setsid_command\" \"$shell_command\" -c \"$watch_script\" \"$watch_name\" \"$runtime\" ' +\n '\"$identity\" \"$$\" \"$supervisor_start\" \"$expected_parent\" \"$expected_parent_start\" \"$stat_command\" ' +\n '\"$rm_command\" \"$sleep_command\" ) </dev/null >/dev/null 2>&1 &',\n \"child=\",\n \"if ! matches_parent; then terminate; fi\",\n '\"$@\" &',\n \"child=$!\",\n \"while :; do\",\n ' wait \"$child\"',\n \" status=$?\",\n ' if kill -0 \"$child\" 2>/dev/null; then continue; fi',\n ' exit \"$status\"',\n \"done\",\n].join(\"\\n\");\n\nconst processIdentity = (pid: number): ProcessIdentity => {\n const fields = readProcessStatFields(pid);\n const startTime = fields?.[19];\n\n if (startTime === undefined || isReapedState(fields?.[0])) {\n throw new Error(`Failed to identify process ${String(pid)}`);\n }\n\n return { pid, startTime };\n};\n\nconst writeGuardCommand = (child: ChildProcess, operation: \"+\" | \"-\", job: GuardJob): void => {\n child.stdin?.write(`${operation}${JSON.stringify(job)}\\n`);\n};\n\nconst releaseGuard = (child: ChildProcess): void => {\n child.unref();\n const { stdin } = child;\n\n if (stdin instanceof Socket) {\n stdin.unref();\n }\n};\n\nconst isSameProcessWatch = (left: ProcessWatch, right: ProcessWatch): boolean =>\n left.owner.pid === right.owner.pid &&\n left.owner.startTime === right.owner.startTime &&\n left.target.pid === right.target.pid &&\n left.target.startTime === right.target.startTime;\n\nconst configureProcessWatch = (watch?: ProcessWatch): void => {\n if (watch === undefined) {\n return;\n }\n\n if (guard.watch !== undefined && !isSameProcessWatch(guard.watch, watch)) {\n throw new Error(\"The process guard is already watching another owner\");\n }\n\n if (guard.child !== undefined && guard.watch === undefined) {\n throw new Error(\"The process guard was started before its owner was registered\");\n }\n\n guard.watch = watch;\n};\n\nconst guardArguments = (): string[] =>\n guard.watch === undefined\n ? [GUARD_PATH, RUN_PREFIX]\n : [GUARD_PATH, RUN_PREFIX, JSON.stringify(guard.watch)];\n\nconst startGuard = (watch?: ProcessWatch): void => {\n configureProcessWatch(watch);\n\n if (guard.child) {\n return;\n }\n\n const child = spawn(process.execPath, guardArguments(), {\n detached: true,\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n\n child.on(\"error\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n child.on(\"exit\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n child.stdin.on(\"error\", () => {\n if (guard.child === child) {\n guard.child = undefined;\n }\n });\n\n guard.child = child;\n\n for (const job of guard.jobs.values()) {\n writeGuardCommand(child, \"+\", job);\n }\n\n releaseGuard(child);\n};\n\nconst watchParentProcess = (): void => {\n const parentId = process.ppid;\n const watch = {\n owner: processIdentity(parentId),\n target: processIdentity(process.pid),\n };\n\n if (process.ppid !== parentId) {\n throw new Error(\"The parent process exited while its guard was starting\");\n }\n\n startGuard(watch);\n};\n\nconst registerJob = (job: GuardJob): void => {\n guard.jobs.set(job.marker, job);\n\n if (guard.child) {\n writeGuardCommand(guard.child, \"+\", job);\n }\n};\n\nconst unregisterJob = (job: GuardJob): void => {\n guard.jobs.delete(job.marker);\n\n if (guard.child) {\n writeGuardCommand(guard.child, \"-\", job);\n }\n};\n\nconst rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] }: SpawnRollback): void => {\n if (processGroup === undefined) {\n child.kill(\"SIGKILL\");\n } else {\n killProcessGroup(processGroup);\n }\n\n killMarkedProcesses(marker);\n\n for (const identity of cleanupDirectories) {\n removeCleanupDirectory(identity);\n }\n};\n\nconst captureCleanupDirectories = (command: string, paths: string[]): CleanupDirectoryIdentity[] => {\n const identities = paths.map((path) => cleanupDirectoryIdentity(path));\n\n if (identities.includes(undefined)) {\n throw new Error(`Failed to identify a cleanup directory for ${command}`);\n }\n\n return identities.filter((identity): identity is CleanupDirectoryIdentity => identity !== undefined);\n};\n\nconst readResource = (path: string): string => {\n try {\n return readFileSync(path, \"utf8\").trim();\n } catch {\n return \"unknown\";\n }\n};\n\nconst openDescriptors = (): string => {\n try {\n return String(readdirSync(\"/proc/self/fd\").length);\n } catch {\n return \"unknown\";\n }\n};\n\nconst resourceSummary = (): string =>\n `open descriptors ${openDescriptors()}, ` +\n `cgroup pids ${readResource(\"/sys/fs/cgroup/pids.current\")}/${readResource(\"/sys/fs/cgroup/pids.max\")}`;\n\nconst spawnedProcessGroup = (child: ChildProcess, command: string): ProcessGroupIdentity | undefined => {\n const processGroupId = child.pid;\n\n if (processGroupId !== undefined) {\n return processGroupIdentity(processGroupId);\n }\n\n child.on(\"error\", (cause: NodeJS.ErrnoException) => {\n warn(`Cannot spawn ${command}: ${cause.code ?? cause.message}`);\n });\n\n return undefined;\n};\n\nconst spawnGuarded = (\n command: string,\n options: ParentDeathSpawnOptions,\n signal: GuardSignal,\n launchArguments: LaunchArguments,\n): ChildProcess => {\n const executable = resolveExecutable(command);\n const setpriv = resolveExecutable(\"setpriv\");\n const jobValue = `${RUN_ID}/${randomBytes(4).toString(\"hex\")}`;\n const marker = `${PROCESS_MARKER}=${jobValue}`;\n const cleanupDirectories = captureCleanupDirectories(command, options.cleanupDirectories ?? []);\n startGuard();\n\n const child = spawn(setpriv, launchArguments(executable, cleanupDirectories), {\n detached: true,\n stdio: options.stdio ?? \"ignore\",\n env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },\n });\n\n const group = spawnedProcessGroup(child, command);\n\n if (group === undefined) {\n rollbackSpawn({ child, marker, cleanupDirectories });\n throw new Error(`Failed to spawn ${command} or identify its process group (${resourceSummary()})`);\n }\n\n const job: GuardJob = { marker, processGroup: group, cleanupDirectories, signal };\n registerJob(job);\n\n child.on(\"exit\", () => {\n killMarkedProcesses(job.marker);\n unregisterJob(job);\n });\n\n return child;\n};\n\nfunction spawnWithParentDeathSignal(\n command: string,\n args: string[],\n options: ParentDeathSpawnOptions = {},\n): ChildProcess {\n return spawnGuarded(\n command,\n options,\n \"SIGKILL\",\n (executable) => [\"--pdeathsig\", \"SIGKILL\", executable, ...args],\n );\n}\n\nconst spawnWithParentDeathSupervisor = (\n command: string,\n args: string[],\n options: ParentDeathSupervisorOptions,\n): ChildProcess => {\n const shell = resolveExecutable(\"sh\");\n const stat = resolveExecutable(\"stat\");\n const rm = resolveExecutable(\"rm\");\n const setsid = resolveExecutable(\"setsid\");\n const sleep = resolveExecutable(\"sleep\");\n\n return spawnGuarded(\n command,\n { ...options, cleanupDirectories: [options.cleanupDirectory] },\n \"SIGCONT\",\n (executable, cleanupDirectories) => {\n const identity = cleanupDirectories[0];\n\n if (identity === undefined) {\n throw new Error(`Failed to identify a cleanup directory for ${command}`);\n }\n\n return [\n \"--pdeathsig\",\n \"SIGCONT\",\n shell,\n \"-c\",\n SUPERVISOR_SCRIPT,\n SUPERVISOR_NAME,\n identity.path,\n `${identity.device}:${identity.inode}:${identity.userId}`,\n String(process.pid),\n processIdentity(process.pid).startTime,\n stat,\n rm,\n shell,\n setsid,\n sleep,\n SUPERVISOR_WATCH_NAME,\n SUPERVISOR_WATCH_SCRIPT,\n SUPERVISOR_CLEANUP_NAME,\n SUPERVISOR_CLEANUP_SCRIPT,\n executable,\n ...args,\n ];\n },\n );\n};\n\nexport { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor, watchParentProcess };\n"]}
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/utils",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Shared utilities for the
|
|
3
|
+
"version": "2.0.0-beta.10",
|
|
4
|
+
"description": "Shared utilities for the GTKX GNOME app toolchain.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
7
7
|
"gtk",
|
|
8
8
|
"gtk4",
|
|
9
|
+
"adwaita",
|
|
10
|
+
"gnome",
|
|
9
11
|
"utils",
|
|
10
12
|
"linux",
|
|
11
13
|
"desktop"
|
|
@@ -26,6 +28,8 @@
|
|
|
26
28
|
"./package.json": "./package.json",
|
|
27
29
|
".": {
|
|
28
30
|
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"require": null,
|
|
29
33
|
"default": "./dist/index.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
@@ -45,10 +49,14 @@
|
|
|
45
49
|
}
|
|
46
50
|
},
|
|
47
51
|
"dependencies": {
|
|
48
|
-
"picocolors": "^1.1.1"
|
|
52
|
+
"picocolors": "^1.1.1",
|
|
53
|
+
"which": "^7.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/which": "^3.0.4"
|
|
49
57
|
},
|
|
50
58
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
59
|
+
"node": ">=26.7.0"
|
|
52
60
|
},
|
|
53
61
|
"scripts": {
|
|
54
62
|
"release": "tsx ../../scripts/release-package.ts"
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,8 @@ export * from "./array/index.ts";
|
|
|
2
2
|
export * from "./class/index.ts";
|
|
3
3
|
export * from "./error/index.ts";
|
|
4
4
|
export * from "./log/index.ts";
|
|
5
|
-
export * from "./map/index.ts";
|
|
6
5
|
export * from "./object/index.ts";
|
|
6
|
+
export * from "./path.ts";
|
|
7
7
|
export * from "./predicate/index.ts";
|
|
8
8
|
export * from "./process/index.ts";
|
|
9
9
|
export * from "./set/index.ts";
|
package/src/path.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { isAbsolute, relative, sep } from "node:path";
|
|
2
|
+
|
|
3
|
+
const isOutsidePath = (path: string): boolean =>
|
|
4
|
+
path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path);
|
|
5
|
+
|
|
6
|
+
const isPathInside = (parent: string, candidate: string): boolean => {
|
|
7
|
+
const path = relative(parent, candidate);
|
|
8
|
+
|
|
9
|
+
return path !== "" && !isOutsidePath(path);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const isPathWithin = (parent: string, candidate: string): boolean =>
|
|
13
|
+
!isOutsidePath(relative(parent, candidate));
|
|
14
|
+
|
|
15
|
+
const toPosixPath = (path: string): string => path.replaceAll("\\", "/");
|
|
16
|
+
|
|
17
|
+
export { isPathInside, isPathWithin, toPosixPath };
|
package/src/process/index.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
export { exitCodeForSignal } from "./exit-code-for-signal.ts";
|
|
2
2
|
export { installGracefulShutdown } from "./install-graceful-shutdown.ts";
|
|
3
|
+
export {
|
|
4
|
+
type CleanupDirectoryIdentity,
|
|
5
|
+
cleanupDirectoryIdentity,
|
|
6
|
+
killProcessGroup,
|
|
7
|
+
type ProcessGroupIdentity,
|
|
8
|
+
processGroupIdentity,
|
|
9
|
+
removeCleanupDirectory,
|
|
10
|
+
} from "./kill-process-group.ts";
|
|
11
|
+
export { isProcessAlive } from "./process-status.ts";
|
|
3
12
|
export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.ts";
|
|
4
|
-
export {
|
|
13
|
+
export {
|
|
14
|
+
spawnWithParentDeathSignal,
|
|
15
|
+
spawnWithParentDeathSupervisor,
|
|
16
|
+
watchParentProcess,
|
|
17
|
+
} from "./spawn-with-parent-death-signal.ts";
|
|
@@ -1,44 +1,89 @@
|
|
|
1
1
|
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { readProcessStatFields } from "./process-status.ts";
|
|
3
|
+
|
|
4
|
+
type ProcessIdentity = {
|
|
5
|
+
pid: number;
|
|
6
|
+
sessionId: number;
|
|
7
|
+
startTime: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type MarkerMatcher = (assignment: string) => boolean;
|
|
2
11
|
|
|
3
12
|
const PROCESS_MARKER = "GTKX_PROCESS_GUARD";
|
|
4
13
|
const MAX_KILL_PASSES = 8;
|
|
14
|
+
const JOB_ID_PATTERN = /^[0-9a-f]{8}$/;
|
|
5
15
|
|
|
6
|
-
const
|
|
16
|
+
const processIdentity = (pid: number): ProcessIdentity | undefined => {
|
|
17
|
+
const fields = readProcessStatFields(pid);
|
|
18
|
+
const sessionId = Number(fields?.[3]);
|
|
19
|
+
const startTime = fields?.[19];
|
|
20
|
+
|
|
21
|
+
return startTime !== undefined && Number.isSafeInteger(sessionId)
|
|
22
|
+
? { pid, sessionId, startTime }
|
|
23
|
+
: undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const isMarked = (pid: number, isMatch: MarkerMatcher): boolean => {
|
|
7
27
|
try {
|
|
8
28
|
return readFileSync(`/proc/${String(pid)}/environ`, "utf8")
|
|
9
29
|
.split("\0")
|
|
10
|
-
.some((assignment) => assignment
|
|
30
|
+
.some((assignment) => isMatch(assignment));
|
|
11
31
|
} catch {
|
|
12
32
|
return false;
|
|
13
33
|
}
|
|
14
34
|
};
|
|
15
35
|
|
|
16
|
-
const
|
|
36
|
+
const markedProcesses = (isMatch: MarkerMatcher): ProcessIdentity[] =>
|
|
17
37
|
readdirSync("/proc")
|
|
18
38
|
.map(Number)
|
|
19
39
|
.filter((pid) => Number.isSafeInteger(pid) && pid > 1 && pid !== process.pid)
|
|
20
|
-
.filter((pid) => isMarked(pid,
|
|
40
|
+
.filter((pid) => isMarked(pid, isMatch))
|
|
41
|
+
.map((pid) => processIdentity(pid))
|
|
42
|
+
.filter((identity): identity is ProcessIdentity => identity !== undefined);
|
|
43
|
+
|
|
44
|
+
const killProcess = (identity: ProcessIdentity, isMatch: MarkerMatcher): void => {
|
|
45
|
+
if (!isMarked(identity.pid, isMatch)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const current = processIdentity(identity.pid);
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
current?.sessionId !== identity.sessionId ||
|
|
53
|
+
current.startTime !== identity.startTime
|
|
54
|
+
) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
21
57
|
|
|
22
|
-
const killPid = (pid: number): void => {
|
|
23
58
|
try {
|
|
24
|
-
process.kill(pid, "SIGKILL");
|
|
59
|
+
process.kill(identity.pid, "SIGKILL");
|
|
25
60
|
} catch {
|
|
26
61
|
return;
|
|
27
62
|
}
|
|
28
63
|
};
|
|
29
64
|
|
|
30
|
-
|
|
65
|
+
const killMatchingProcesses = (isMatch: MarkerMatcher): void => {
|
|
31
66
|
for (let pass = 0; pass < MAX_KILL_PASSES; pass += 1) {
|
|
32
|
-
const
|
|
67
|
+
const processes = markedProcesses(isMatch);
|
|
33
68
|
|
|
34
|
-
if (
|
|
69
|
+
if (processes.length === 0) {
|
|
35
70
|
return;
|
|
36
71
|
}
|
|
37
72
|
|
|
38
|
-
for (const
|
|
39
|
-
|
|
73
|
+
for (const identity of processes) {
|
|
74
|
+
killProcess(identity, isMatch);
|
|
40
75
|
}
|
|
41
76
|
}
|
|
42
|
-
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const killMarkedProcesses = (marker: string): void => {
|
|
80
|
+
killMatchingProcesses((assignment) => assignment === marker);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const killMarkedProcessRun = (runPrefix: string): void => {
|
|
84
|
+
killMatchingProcesses((assignment) =>
|
|
85
|
+
assignment.startsWith(runPrefix) && JOB_ID_PATTERN.test(assignment.slice(runPrefix.length)),
|
|
86
|
+
);
|
|
87
|
+
};
|
|
43
88
|
|
|
44
|
-
export { PROCESS_MARKER, killMarkedProcesses };
|
|
89
|
+
export { PROCESS_MARKER, killMarkedProcessRun, killMarkedProcesses };
|