@gtkx/utils 2.0.0-beta.2 → 2.0.0-beta.4

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.
@@ -1,6 +1,6 @@
1
1
  export { exitCodeForSignal } from "./exit-code-for-signal.ts";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.ts";
3
- export { killProcessGroup, type ProcessGroupIdentity, processGroupIdentity, } from "./kill-process-group.ts";
3
+ export { type CleanupDirectoryIdentity, cleanupDirectoryIdentity, killProcessGroup, type ProcessGroupIdentity, processGroupIdentity, removeCleanupDirectory, } from "./kill-process-group.ts";
4
4
  export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.ts";
5
- export { spawnWithParentDeathSignal } from "./spawn-with-parent-death-signal.ts";
5
+ export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor, watchParentProcess, } from "./spawn-with-parent-death-signal.ts";
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EACH,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,oBAAoB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EACH,KAAK,wBAAwB,EAC7B,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,sBAAsB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EACH,0BAA0B,EAC1B,8BAA8B,EAC9B,kBAAkB,GACrB,MAAM,qCAAqC,CAAC"}
@@ -1,6 +1,6 @@
1
1
  export { exitCodeForSignal } from "./exit-code-for-signal.js";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.js";
3
- export { killProcessGroup, processGroupIdentity, } from "./kill-process-group.js";
3
+ export { cleanupDirectoryIdentity, killProcessGroup, processGroupIdentity, removeCleanupDirectory, } from "./kill-process-group.js";
4
4
  export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.js";
5
- export { spawnWithParentDeathSignal } from "./spawn-with-parent-death-signal.js";
5
+ export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor, watchParentProcess, } from "./spawn-with-parent-death-signal.js";
6
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EACH,gBAAgB,EAEhB,oBAAoB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC","sourcesContent":["export { exitCodeForSignal } from \"./exit-code-for-signal.ts\";\nexport { installGracefulShutdown } from \"./install-graceful-shutdown.ts\";\nexport {\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n} from \"./kill-process-group.ts\";\nexport { resolveExecutable, tryResolveExecutable } from \"./resolve-executable.ts\";\nexport { spawnWithParentDeathSignal } from \"./spawn-with-parent-death-signal.ts\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/process/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAEH,wBAAwB,EACxB,gBAAgB,EAEhB,oBAAoB,EACpB,sBAAsB,GACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EACH,0BAA0B,EAC1B,8BAA8B,EAC9B,kBAAkB,GACrB,MAAM,qCAAqC,CAAC","sourcesContent":["export { exitCodeForSignal } from \"./exit-code-for-signal.ts\";\nexport { installGracefulShutdown } from \"./install-graceful-shutdown.ts\";\nexport {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.ts\";\nexport { resolveExecutable, tryResolveExecutable } from \"./resolve-executable.ts\";\nexport {\n spawnWithParentDeathSignal,\n spawnWithParentDeathSupervisor,\n watchParentProcess,\n} from \"./spawn-with-parent-death-signal.ts\";\n"]}
@@ -9,7 +9,7 @@ type CleanupDirectoryIdentity = {
9
9
  userId: string;
10
10
  };
11
11
  declare const processGroupIdentity: (processGroupId: number) => ProcessGroupIdentity | undefined;
12
- declare const killProcessGroup: (identity: ProcessGroupIdentity) => void;
12
+ declare const killProcessGroup: (identity: ProcessGroupIdentity, signal?: NodeJS.Signals) => void;
13
13
  declare const cleanupDirectoryIdentity: (path: string) => CleanupDirectoryIdentity | undefined;
14
14
  declare const removeCleanupDirectory: (identity: CleanupDirectoryIdentity) => void;
15
15
  export { type CleanupDirectoryIdentity, cleanupDirectoryIdentity, killProcessGroup, type ProcessGroupIdentity, processGroupIdentity, removeCleanupDirectory, };
@@ -1 +1 @@
1
- {"version":3,"file":"kill-process-group.d.ts","sourceRoot":"","sources":["../../src/process/kill-process-group.ts"],"names":[],"mappings":"AAGA,KAAK,oBAAoB,GAAG;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,QAAA,MAAM,oBAAoB,GAAI,gBAAgB,MAAM,KAAG,oBAAoB,GAAG,SAkB7E,CAAC;AAQF,QAAA,MAAM,gBAAgB,GAAI,UAAU,oBAAoB,KAAG,IAU1D,CAAC;AAEF,QAAA,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,wBAAwB,GAAG,SAqB3E,CAAC;AAEF,QAAA,MAAM,sBAAsB,GAAI,UAAU,wBAAwB,KAAG,IAgBpE,CAAC;AAEF,OAAO,EACH,KAAK,wBAAwB,EAC7B,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,sBAAsB,GACzB,CAAC"}
1
+ {"version":3,"file":"kill-process-group.d.ts","sourceRoot":"","sources":["../../src/process/kill-process-group.ts"],"names":[],"mappings":"AAGA,KAAK,oBAAoB,GAAG;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAIF,QAAA,MAAM,oBAAoB,GAAI,gBAAgB,MAAM,KAAG,oBAAoB,GAAG,SAkB7E,CAAC;AAQF,QAAA,MAAM,gBAAgB,GAAI,UAAU,oBAAoB,EAAE,SAAQ,MAAM,CAAC,OAAmB,KAAG,IAU9F,CAAC;AAEF,QAAA,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,wBAAwB,GAAG,SAqB3E,CAAC;AAEF,QAAA,MAAM,sBAAsB,GAAI,UAAU,wBAAwB,KAAG,IAgBpE,CAAC;AAEF,OAAO,EACH,KAAK,wBAAwB,EAC7B,wBAAwB,EACxB,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,sBAAsB,GACzB,CAAC"}
@@ -23,12 +23,12 @@ const isCurrentProcessGroup = (identity) => {
23
23
  const current = processGroupIdentity(identity.processGroupId);
24
24
  return current?.leaderStartTime === identity.leaderStartTime;
25
25
  };
26
- const killProcessGroup = (identity) => {
26
+ const killProcessGroup = (identity, signal = "SIGKILL") => {
27
27
  if (!isCurrentProcessGroup(identity)) {
28
28
  return;
29
29
  }
30
30
  try {
31
- process.kill(-identity.processGroupId, "SIGKILL");
31
+ process.kill(-identity.processGroupId, signal);
32
32
  }
33
33
  catch {
34
34
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"kill-process-group.js","sourceRoot":"","sources":["../../src/process/kill-process-group.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstatSync, readFileSync, rmSync } from \"node:fs\";\nimport { isAbsolute, resolve } from \"node:path\";\n\ntype ProcessGroupIdentity = {\n processGroupId: number;\n leaderStartTime: string;\n};\n\ntype CleanupDirectoryIdentity = {\n path: string;\n device: string;\n inode: string;\n userId: string;\n};\n\nconst isProcessGroupId = (value: number): boolean => Number.isSafeInteger(value) && value > 1;\n\nconst processGroupIdentity = (processGroupId: number): ProcessGroupIdentity | undefined => {\n if (!isProcessGroupId(processGroupId)) {\n return undefined;\n }\n\n try {\n const stat = readFileSync(`/proc/${String(processGroupId)}/stat`, \"utf8\");\n const fields = stat.slice(stat.lastIndexOf(\") \") + 2).split(\" \");\n const actualProcessGroupId = Number(fields[2]);\n const sessionId = Number(fields[3]);\n const leaderStartTime = fields[19];\n\n return actualProcessGroupId === processGroupId && sessionId === processGroupId && leaderStartTime !== undefined\n ? { processGroupId, leaderStartTime }\n : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst isCurrentProcessGroup = (identity: ProcessGroupIdentity): boolean => {\n const current = processGroupIdentity(identity.processGroupId);\n\n return current?.leaderStartTime === identity.leaderStartTime;\n};\n\nconst killProcessGroup = (identity: ProcessGroupIdentity): void => {\n if (!isCurrentProcessGroup(identity)) {\n return;\n }\n\n try {\n process.kill(-identity.processGroupId, \"SIGKILL\");\n } catch {\n return;\n }\n};\n\nconst cleanupDirectoryIdentity = (path: string): CleanupDirectoryIdentity | undefined => {\n const absolutePath = resolve(path);\n\n if (absolutePath === \"/\" || !isAbsolute(path)) {\n return undefined;\n }\n\n try {\n const entry = lstatSync(absolutePath, { bigint: true });\n\n return entry.isDirectory()\n ? {\n path: absolutePath,\n device: entry.dev.toString(),\n inode: entry.ino.toString(),\n userId: entry.uid.toString(),\n }\n : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst removeCleanupDirectory = (identity: CleanupDirectoryIdentity): void => {\n const current = cleanupDirectoryIdentity(identity.path);\n\n if (\n current?.device !== identity.device ||\n current.inode !== identity.inode ||\n current.userId !== identity.userId\n ) {\n return;\n }\n\n try {\n rmSync(identity.path, { recursive: true, force: true });\n } catch {\n return;\n }\n};\n\nexport {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n};\n"]}
1
+ {"version":3,"file":"kill-process-group.js","sourceRoot":"","sources":["../../src/process/kill-process-group.ts"],"names":[],"mappings":"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","sourcesContent":["import { lstatSync, readFileSync, rmSync } from \"node:fs\";\nimport { isAbsolute, resolve } from \"node:path\";\n\ntype ProcessGroupIdentity = {\n processGroupId: number;\n leaderStartTime: string;\n};\n\ntype CleanupDirectoryIdentity = {\n path: string;\n device: string;\n inode: string;\n userId: string;\n};\n\nconst isProcessGroupId = (value: number): boolean => Number.isSafeInteger(value) && value > 1;\n\nconst processGroupIdentity = (processGroupId: number): ProcessGroupIdentity | undefined => {\n if (!isProcessGroupId(processGroupId)) {\n return undefined;\n }\n\n try {\n const stat = readFileSync(`/proc/${String(processGroupId)}/stat`, \"utf8\");\n const fields = stat.slice(stat.lastIndexOf(\") \") + 2).split(\" \");\n const actualProcessGroupId = Number(fields[2]);\n const sessionId = Number(fields[3]);\n const leaderStartTime = fields[19];\n\n return actualProcessGroupId === processGroupId && sessionId === processGroupId && leaderStartTime !== undefined\n ? { processGroupId, leaderStartTime }\n : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst isCurrentProcessGroup = (identity: ProcessGroupIdentity): boolean => {\n const current = processGroupIdentity(identity.processGroupId);\n\n return current?.leaderStartTime === identity.leaderStartTime;\n};\n\nconst killProcessGroup = (identity: ProcessGroupIdentity, signal: NodeJS.Signals = \"SIGKILL\"): void => {\n if (!isCurrentProcessGroup(identity)) {\n return;\n }\n\n try {\n process.kill(-identity.processGroupId, signal);\n } catch {\n return;\n }\n};\n\nconst cleanupDirectoryIdentity = (path: string): CleanupDirectoryIdentity | undefined => {\n const absolutePath = resolve(path);\n\n if (absolutePath === \"/\" || !isAbsolute(path)) {\n return undefined;\n }\n\n try {\n const entry = lstatSync(absolutePath, { bigint: true });\n\n return entry.isDirectory()\n ? {\n path: absolutePath,\n device: entry.dev.toString(),\n inode: entry.ino.toString(),\n userId: entry.uid.toString(),\n }\n : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst removeCleanupDirectory = (identity: CleanupDirectoryIdentity): void => {\n const current = cleanupDirectoryIdentity(identity.path);\n\n if (\n current?.device !== identity.device ||\n current.inode !== identity.inode ||\n current.userId !== identity.userId\n ) {\n return;\n }\n\n try {\n rmSync(identity.path, { recursive: true, force: true });\n } catch {\n return;\n }\n};\n\nexport {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n};\n"]}
@@ -1,13 +1,24 @@
1
+ import { readFileSync } from "node:fs";
1
2
  import { killMarkedProcesses, killMarkedProcessRun } from "./kill-marked-processes.js";
2
- import { killProcessGroup, removeCleanupDirectory, } from "./kill-process-group.js";
3
+ import { killProcessGroup, processGroupIdentity, removeCleanupDirectory, } from "./kill-process-group.js";
3
4
  const GUARD_PREFIX = process.argv[2] ?? "";
5
+ const PROCESS_WATCH_ARGUMENT = process.argv[3];
4
6
  const WATCHED_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
7
+ const OWNER_POLL_INTERVAL_MS = 50;
8
+ const SUPERVISOR_EXIT_TIMEOUT_MS = 2000;
5
9
  const state = {
6
10
  bufferedCommands: "",
7
11
  isSweeping: false,
8
12
  jobs: new Map(),
9
13
  };
10
14
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15
+ const isProcessIdentity = (value) => isRecord(value) &&
16
+ typeof value.pid === "number" &&
17
+ Number.isSafeInteger(value.pid) &&
18
+ value.pid > 1 &&
19
+ typeof value.startTime === "string" &&
20
+ /^\d+$/.test(value.startTime);
21
+ const isProcessWatch = (value) => isRecord(value) && isProcessIdentity(value.owner) && isProcessIdentity(value.target);
11
22
  const isProcessGroupIdentity = (value) => isRecord(value) &&
12
23
  typeof value.processGroupId === "number" &&
13
24
  Number.isSafeInteger(value.processGroupId) &&
@@ -23,7 +34,46 @@ const isGuardJob = (value) => isRecord(value) &&
23
34
  typeof value.marker === "string" &&
24
35
  isProcessGroupIdentity(value.processGroup) &&
25
36
  Array.isArray(value.cleanupDirectories) &&
26
- value.cleanupDirectories.every(isCleanupDirectoryIdentity);
37
+ value.cleanupDirectories.every(isCleanupDirectoryIdentity) &&
38
+ (value.signal === "SIGKILL" || value.signal === "SIGCONT");
39
+ const parseProcessWatch = () => {
40
+ if (PROCESS_WATCH_ARGUMENT === undefined) {
41
+ return undefined;
42
+ }
43
+ try {
44
+ const value = JSON.parse(PROCESS_WATCH_ARGUMENT);
45
+ return isProcessWatch(value) ? value : undefined;
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ };
51
+ const currentProcessIdentity = (pid) => {
52
+ try {
53
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
54
+ const fields = stat.slice(stat.lastIndexOf(") ") + 2).split(" ", 20);
55
+ const state = fields[0];
56
+ const startTime = fields[19];
57
+ return startTime !== undefined && state !== undefined && !["Z", "X", "x"].includes(state)
58
+ ? { pid, startTime }
59
+ : undefined;
60
+ }
61
+ catch {
62
+ return undefined;
63
+ }
64
+ };
65
+ const isCurrentProcess = (identity) => currentProcessIdentity(identity.pid)?.startTime === identity.startTime;
66
+ const killProcess = (identity) => {
67
+ if (!isCurrentProcess(identity)) {
68
+ return;
69
+ }
70
+ try {
71
+ process.kill(identity.pid, "SIGKILL");
72
+ }
73
+ catch {
74
+ return;
75
+ }
76
+ };
27
77
  const applyCommand = (command) => {
28
78
  const operation = command[0];
29
79
  let value;
@@ -53,8 +103,10 @@ const receiveCommands = (chunk) => {
53
103
  };
54
104
  const killJobs = () => {
55
105
  for (const job of state.jobs.values()) {
56
- killProcessGroup(job.processGroup);
57
- killMarkedProcesses(job.marker);
106
+ killProcessGroup(job.processGroup, job.signal);
107
+ if (job.signal === "SIGKILL") {
108
+ killMarkedProcesses(job.marker);
109
+ }
58
110
  }
59
111
  };
60
112
  const removeCleanupDirectories = () => {
@@ -68,24 +120,83 @@ const removeCleanupDirectories = () => {
68
120
  removeCleanupDirectory(identity);
69
121
  }
70
122
  };
71
- const sweep = () => {
123
+ const hasRunningSupervisor = () => {
124
+ for (const job of state.jobs.values()) {
125
+ if (job.signal !== "SIGCONT") {
126
+ continue;
127
+ }
128
+ const current = processGroupIdentity(job.processGroup.processGroupId);
129
+ if (current?.leaderStartTime === job.processGroup.leaderStartTime) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ };
135
+ const forceSupervisors = () => {
136
+ for (const job of state.jobs.values()) {
137
+ if (job.signal === "SIGCONT") {
138
+ killProcessGroup(job.processGroup);
139
+ }
140
+ }
141
+ };
142
+ const finishSweep = () => {
143
+ if (GUARD_PREFIX.length > 0) {
144
+ killMarkedProcessRun(GUARD_PREFIX);
145
+ }
146
+ removeCleanupDirectories();
147
+ process.exit(0);
148
+ };
149
+ const awaitSupervisors = (deadline) => {
150
+ if (hasRunningSupervisor() && Date.now() < deadline) {
151
+ setTimeout(() => {
152
+ awaitSupervisors(deadline);
153
+ }, OWNER_POLL_INTERVAL_MS);
154
+ return;
155
+ }
156
+ forceSupervisors();
157
+ finishSweep();
158
+ };
159
+ const sweep = (target) => {
72
160
  if (state.isSweeping) {
73
161
  return;
74
162
  }
75
163
  state.isSweeping = true;
76
164
  applyCommand(state.bufferedCommands);
165
+ if (target !== undefined) {
166
+ killProcess(target);
167
+ }
77
168
  killJobs();
78
- if (GUARD_PREFIX.length > 0) {
79
- killMarkedProcessRun(GUARD_PREFIX);
169
+ awaitSupervisors(Date.now() + SUPERVISOR_EXIT_TIMEOUT_MS);
170
+ };
171
+ const watch = parseProcessWatch();
172
+ const startOwnerPoll = () => {
173
+ if (watch === undefined) {
174
+ return;
175
+ }
176
+ const ownerPoll = setInterval(() => {
177
+ if (isCurrentProcess(watch.owner)) {
178
+ return;
179
+ }
180
+ clearInterval(ownerPoll);
181
+ sweep(watch.target);
182
+ }, OWNER_POLL_INTERVAL_MS);
183
+ if (!isCurrentProcess(watch.owner)) {
184
+ clearInterval(ownerPoll);
185
+ sweep(watch.target);
80
186
  }
81
- removeCleanupDirectories();
82
- process.exit(0);
83
187
  };
188
+ startOwnerPoll();
84
189
  process.stdin.resume();
85
190
  process.stdin.on("data", receiveCommands);
86
- process.stdin.on("end", sweep);
87
- process.stdin.on("error", sweep);
191
+ process.stdin.on("end", () => {
192
+ sweep();
193
+ });
194
+ process.stdin.on("error", () => {
195
+ sweep();
196
+ });
88
197
  for (const signal of WATCHED_SIGNALS) {
89
- process.on(signal, sweep);
198
+ process.on(signal, () => {
199
+ sweep();
200
+ });
90
201
  }
91
202
  //# sourceMappingURL=process-guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"process-guard.js","sourceRoot":"","sources":["../../src/process/process-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { killMarkedProcesses, killMarkedProcessRun } from \"./kill-marked-processes.ts\";\nimport {\n type CleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.ts\";\n\nconst GUARD_PREFIX = process.argv[2] ?? \"\";\nconst WATCHED_SIGNALS = [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"] as const satisfies NodeJS.Signals[];\ntype GuardJob = {\n marker: string;\n processGroup: ProcessGroupIdentity;\n cleanupDirectories: CleanupDirectoryIdentity[];\n};\n\nconst state: { bufferedCommands: string; isSweeping: boolean; jobs: Map<string, GuardJob> } = {\n bufferedCommands: \"\",\n isSweeping: false,\n jobs: new Map(),\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isProcessGroupIdentity = (value: unknown): value is ProcessGroupIdentity =>\n isRecord(value) &&\n typeof value.processGroupId === \"number\" &&\n Number.isSafeInteger(value.processGroupId) &&\n value.processGroupId > 1 &&\n typeof value.leaderStartTime === \"string\" &&\n /^\\d+$/.test(value.leaderStartTime);\n\nconst isCleanupDirectoryIdentity = (value: unknown): value is CleanupDirectoryIdentity =>\n isRecord(value) &&\n typeof value.path === \"string\" &&\n typeof value.device === \"string\" &&\n typeof value.inode === \"string\" &&\n typeof value.userId === \"string\";\n\nconst isGuardJob = (value: unknown): value is GuardJob =>\n isRecord(value) &&\n typeof value.marker === \"string\" &&\n isProcessGroupIdentity(value.processGroup) &&\n Array.isArray(value.cleanupDirectories) &&\n value.cleanupDirectories.every(isCleanupDirectoryIdentity);\n\nconst applyCommand = (command: string): void => {\n const operation = command[0];\n let value: unknown;\n\n try {\n value = JSON.parse(command.slice(1));\n } catch {\n return;\n }\n\n if (!isGuardJob(value)) {\n return;\n }\n\n if (operation === \"+\") {\n state.jobs.set(value.marker, value);\n } else if (operation === \"-\") {\n state.jobs.delete(value.marker);\n }\n};\n\nconst receiveCommands = (chunk: Buffer | string): void => {\n state.bufferedCommands += chunk.toString();\n const commands = state.bufferedCommands.split(\"\\n\");\n state.bufferedCommands = commands.pop() ?? \"\";\n\n for (const command of commands) {\n applyCommand(command);\n }\n};\n\nconst killJobs = (): void => {\n for (const job of state.jobs.values()) {\n killProcessGroup(job.processGroup);\n killMarkedProcesses(job.marker);\n }\n};\n\nconst removeCleanupDirectories = (): void => {\n const cleanupDirectories: Map<string, CleanupDirectoryIdentity> = new Map();\n\n for (const job of state.jobs.values()) {\n for (const identity of job.cleanupDirectories) {\n cleanupDirectories.set(`${identity.device}:${identity.inode}`, identity);\n }\n }\n\n for (const identity of cleanupDirectories.values()) {\n removeCleanupDirectory(identity);\n }\n};\n\nconst sweep = (): void => {\n if (state.isSweeping) {\n return;\n }\n\n state.isSweeping = true;\n applyCommand(state.bufferedCommands);\n killJobs();\n\n if (GUARD_PREFIX.length > 0) {\n killMarkedProcessRun(GUARD_PREFIX);\n }\n\n removeCleanupDirectories();\n process.exit(0);\n};\n\nprocess.stdin.resume();\nprocess.stdin.on(\"data\", receiveCommands);\nprocess.stdin.on(\"end\", sweep);\nprocess.stdin.on(\"error\", sweep);\n\nfor (const signal of WATCHED_SIGNALS) {\n process.on(signal, sweep);\n}\n"]}
1
+ {"version":3,"file":"process-guard.js","sourceRoot":"","sources":["../../src/process/process-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { killMarkedProcesses, killMarkedProcessRun } from \"./kill-marked-processes.ts\";\nimport {\n type CleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.ts\";\n\nconst GUARD_PREFIX = process.argv[2] ?? \"\";\nconst PROCESS_WATCH_ARGUMENT = process.argv[3];\nconst WATCHED_SIGNALS = [\"SIGTERM\", \"SIGINT\", \"SIGHUP\"] as const satisfies NodeJS.Signals[];\nconst OWNER_POLL_INTERVAL_MS = 50;\nconst SUPERVISOR_EXIT_TIMEOUT_MS = 2000;\n\ntype ProcessIdentity = {\n pid: number;\n startTime: string;\n};\n\ntype ProcessWatch = {\n owner: ProcessIdentity;\n target: ProcessIdentity;\n};\n\ntype GuardJob = {\n marker: string;\n processGroup: ProcessGroupIdentity;\n cleanupDirectories: CleanupDirectoryIdentity[];\n signal: NodeJS.Signals;\n};\n\nconst state: { bufferedCommands: string; isSweeping: boolean; jobs: Map<string, GuardJob> } = {\n bufferedCommands: \"\",\n isSweeping: false,\n jobs: new Map(),\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isProcessIdentity = (value: unknown): value is ProcessIdentity =>\n isRecord(value) &&\n typeof value.pid === \"number\" &&\n Number.isSafeInteger(value.pid) &&\n value.pid > 1 &&\n typeof value.startTime === \"string\" &&\n /^\\d+$/.test(value.startTime);\n\nconst isProcessWatch = (value: unknown): value is ProcessWatch =>\n isRecord(value) && isProcessIdentity(value.owner) && isProcessIdentity(value.target);\n\nconst isProcessGroupIdentity = (value: unknown): value is ProcessGroupIdentity =>\n isRecord(value) &&\n typeof value.processGroupId === \"number\" &&\n Number.isSafeInteger(value.processGroupId) &&\n value.processGroupId > 1 &&\n typeof value.leaderStartTime === \"string\" &&\n /^\\d+$/.test(value.leaderStartTime);\n\nconst isCleanupDirectoryIdentity = (value: unknown): value is CleanupDirectoryIdentity =>\n isRecord(value) &&\n typeof value.path === \"string\" &&\n typeof value.device === \"string\" &&\n typeof value.inode === \"string\" &&\n typeof value.userId === \"string\";\n\nconst isGuardJob = (value: unknown): value is GuardJob =>\n isRecord(value) &&\n typeof value.marker === \"string\" &&\n isProcessGroupIdentity(value.processGroup) &&\n Array.isArray(value.cleanupDirectories) &&\n value.cleanupDirectories.every(isCleanupDirectoryIdentity) &&\n (value.signal === \"SIGKILL\" || value.signal === \"SIGCONT\");\n\nconst parseProcessWatch = (): ProcessWatch | undefined => {\n if (PROCESS_WATCH_ARGUMENT === undefined) {\n return undefined;\n }\n\n try {\n const value: unknown = JSON.parse(PROCESS_WATCH_ARGUMENT);\n\n return isProcessWatch(value) ? value : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst currentProcessIdentity = (pid: number): ProcessIdentity | undefined => {\n try {\n const stat = readFileSync(`/proc/${String(pid)}/stat`, \"utf8\");\n const fields = stat.slice(stat.lastIndexOf(\") \") + 2).split(\" \", 20);\n const state = fields[0];\n const startTime = fields[19];\n\n return startTime !== undefined && state !== undefined && ![\"Z\", \"X\", \"x\"].includes(state)\n ? { pid, startTime }\n : undefined;\n } catch {\n return undefined;\n }\n};\n\nconst isCurrentProcess = (identity: ProcessIdentity): boolean =>\n currentProcessIdentity(identity.pid)?.startTime === identity.startTime;\n\nconst killProcess = (identity: ProcessIdentity): void => {\n if (!isCurrentProcess(identity)) {\n return;\n }\n\n try {\n process.kill(identity.pid, \"SIGKILL\");\n } catch {\n return;\n }\n};\n\nconst applyCommand = (command: string): void => {\n const operation = command[0];\n let value: unknown;\n\n try {\n value = JSON.parse(command.slice(1));\n } catch {\n return;\n }\n\n if (!isGuardJob(value)) {\n return;\n }\n\n if (operation === \"+\") {\n state.jobs.set(value.marker, value);\n } else if (operation === \"-\") {\n state.jobs.delete(value.marker);\n }\n};\n\nconst receiveCommands = (chunk: Buffer | string): void => {\n state.bufferedCommands += chunk.toString();\n const commands = state.bufferedCommands.split(\"\\n\");\n state.bufferedCommands = commands.pop() ?? \"\";\n\n for (const command of commands) {\n applyCommand(command);\n }\n};\n\nconst killJobs = (): void => {\n for (const job of state.jobs.values()) {\n killProcessGroup(job.processGroup, job.signal);\n\n if (job.signal === \"SIGKILL\") {\n killMarkedProcesses(job.marker);\n }\n }\n};\n\nconst removeCleanupDirectories = (): void => {\n const cleanupDirectories: Map<string, CleanupDirectoryIdentity> = new Map();\n\n for (const job of state.jobs.values()) {\n for (const identity of job.cleanupDirectories) {\n cleanupDirectories.set(`${identity.device}:${identity.inode}`, identity);\n }\n }\n\n for (const identity of cleanupDirectories.values()) {\n removeCleanupDirectory(identity);\n }\n};\n\nconst hasRunningSupervisor = (): boolean => {\n for (const job of state.jobs.values()) {\n if (job.signal !== \"SIGCONT\") {\n continue;\n }\n\n const current = processGroupIdentity(job.processGroup.processGroupId);\n\n if (current?.leaderStartTime === job.processGroup.leaderStartTime) {\n return true;\n }\n }\n\n return false;\n};\n\nconst forceSupervisors = (): void => {\n for (const job of state.jobs.values()) {\n if (job.signal === \"SIGCONT\") {\n killProcessGroup(job.processGroup);\n }\n }\n};\n\nconst finishSweep = (): void => {\n if (GUARD_PREFIX.length > 0) {\n killMarkedProcessRun(GUARD_PREFIX);\n }\n\n removeCleanupDirectories();\n process.exit(0);\n};\n\nconst awaitSupervisors = (deadline: number): void => {\n if (hasRunningSupervisor() && Date.now() < deadline) {\n setTimeout(() => {\n awaitSupervisors(deadline);\n }, OWNER_POLL_INTERVAL_MS);\n\n return;\n }\n\n forceSupervisors();\n finishSweep();\n};\n\nconst sweep = (target?: ProcessIdentity): void => {\n if (state.isSweeping) {\n return;\n }\n\n state.isSweeping = true;\n applyCommand(state.bufferedCommands);\n\n if (target !== undefined) {\n killProcess(target);\n }\n\n killJobs();\n awaitSupervisors(Date.now() + SUPERVISOR_EXIT_TIMEOUT_MS);\n};\n\nconst watch = parseProcessWatch();\n\nconst startOwnerPoll = (): void => {\n if (watch === undefined) {\n return;\n }\n\n const ownerPoll = setInterval(() => {\n if (isCurrentProcess(watch.owner)) {\n return;\n }\n\n clearInterval(ownerPoll);\n sweep(watch.target);\n }, OWNER_POLL_INTERVAL_MS);\n\n if (!isCurrentProcess(watch.owner)) {\n clearInterval(ownerPoll);\n sweep(watch.target);\n }\n};\n\nstartOwnerPoll();\n\nprocess.stdin.resume();\nprocess.stdin.on(\"data\", receiveCommands);\nprocess.stdin.on(\"end\", () => {\n sweep();\n});\nprocess.stdin.on(\"error\", () => {\n sweep();\n});\n\nfor (const signal of WATCHED_SIGNALS) {\n process.on(signal, () => {\n sweep();\n });\n}\n"]}
@@ -4,6 +4,11 @@ type ParentDeathSpawnOptions = {
4
4
  env?: NodeJS.ProcessEnv;
5
5
  cleanupDirectories?: string[];
6
6
  };
7
+ type ParentDeathSupervisorOptions = Omit<ParentDeathSpawnOptions, "cleanupDirectories"> & {
8
+ cleanupDirectory: string;
9
+ };
10
+ declare const watchParentProcess: () => void;
7
11
  declare function spawnWithParentDeathSignal(command: string, args: string[], options?: ParentDeathSpawnOptions): ChildProcess;
8
- export { spawnWithParentDeathSignal };
12
+ declare const spawnWithParentDeathSupervisor: (command: string, args: string[], options: ParentDeathSupervisorOptions) => ChildProcess;
13
+ export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor, watchParentProcess };
9
14
  //# sourceMappingURL=spawn-with-parent-death-signal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spawn-with-parent-death-signal.d.ts","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAgBjF,KAAK,uBAAuB,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,CAAC;AA0GF,iBAAS,0BAA0B,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,uBAA4B,GACtC,YAAY,CA4Cd;AAED,OAAO,EAAE,0BAA0B,EAAE,CAAC"}
1
+ {"version":3,"file":"spawn-with-parent-death-signal.d.ts","sourceRoot":"","sources":["../../src/process/spawn-with-parent-death-signal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAiBjF,KAAK,uBAAuB,GAAG;IAC3B,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,CAAC;AAEF,KAAK,4BAA4B,GAAG,IAAI,CAAC,uBAAuB,EAAE,oBAAoB,CAAC,GAAG;IACtF,gBAAgB,EAAE,MAAM,CAAC;CAC5B,CAAC;AAmQF,QAAA,MAAM,kBAAkB,QAAO,IAY9B,CAAC;AAgFF,iBAAS,0BAA0B,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,uBAA4B,GACtC,YAAY,CAOd;AAED,QAAA,MAAM,8BAA8B,GAChC,SAAS,MAAM,EACf,MAAM,MAAM,EAAE,EACd,SAAS,4BAA4B,KACtC,YA2CF,CAAC;AAEF,OAAO,EAAE,0BAA0B,EAAE,8BAA8B,EAAE,kBAAkB,EAAE,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { randomBytes } from "node:crypto";
3
+ import { 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";
@@ -11,6 +12,141 @@ const GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PAT
11
12
  const RUN_ID = randomBytes(8).toString("hex");
12
13
  const RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;
13
14
  const guard = { jobs: new Map() };
15
+ const SUPERVISOR_NAME = "gtkx-process-supervisor";
16
+ const SUPERVISOR_CLEANUP_NAME = "gtkx-process-cleanup";
17
+ const SUPERVISOR_WATCH_NAME = "gtkx-process-watch";
18
+ const SUPERVISOR_CLEANUP_BODY = [
19
+ "remaining=40",
20
+ 'while [ "$remaining" -gt 0 ] && kill -KILL "-$group" 2>/dev/null; do',
21
+ ' "$sleep_command" 0.025',
22
+ " remaining=$((remaining - 1))",
23
+ "done",
24
+ "remaining=40",
25
+ 'while [ "$remaining" -gt 0 ] && ' +
26
+ '[ "$("$stat_command" -c "%d:%i:%u" -- "$runtime" 2>/dev/null)" = "$identity" ]; do',
27
+ ' "$rm_command" -rf -- "$runtime"',
28
+ ' "$sleep_command" 0.025',
29
+ " remaining=$((remaining - 1))",
30
+ "done",
31
+ ];
32
+ const SUPERVISOR_CLEANUP_SCRIPT = [
33
+ "runtime=$1",
34
+ "identity=$2",
35
+ "group=$3",
36
+ "stat_command=$4",
37
+ "rm_command=$5",
38
+ "sleep_command=$6",
39
+ ...SUPERVISOR_CLEANUP_BODY,
40
+ ].join("\n");
41
+ const SUPERVISOR_WATCH_SCRIPT = [
42
+ "runtime=$1",
43
+ "identity=$2",
44
+ "group=$3",
45
+ "group_start=$4",
46
+ "expected_parent=$5",
47
+ "expected_parent_start=$6",
48
+ "stat_command=$7",
49
+ "rm_command=$8",
50
+ "sleep_command=$9",
51
+ "matches_process() {",
52
+ " expected_process=$1",
53
+ " expected_start=$2",
54
+ " process_stat=",
55
+ ' IFS= read -r process_stat < "/proc/$expected_process/stat" || return 1',
56
+ " process_tail=${process_stat##*) }",
57
+ " set -- $process_tail",
58
+ ' case "$1" in Z|X|x) return 1 ;; esac',
59
+ ' [ "${20}" = "$expected_start" ]',
60
+ "}",
61
+ "cleanup() {",
62
+ ' trap "" TERM INT HUP',
63
+ ...SUPERVISOR_CLEANUP_BODY.map((line) => ` ${line}`),
64
+ " exit 0",
65
+ "}",
66
+ "trap cleanup TERM INT HUP",
67
+ 'while matches_process "$group" "$group_start" && ' +
68
+ 'matches_process "$expected_parent" "$expected_parent_start"; do',
69
+ ' "$sleep_command" 0.1',
70
+ "done",
71
+ "cleanup",
72
+ ].join("\n");
73
+ const SUPERVISOR_SCRIPT = [
74
+ "runtime=$1",
75
+ "identity=$2",
76
+ "expected_parent=$3",
77
+ "expected_parent_start=$4",
78
+ "stat_command=$5",
79
+ "rm_command=$6",
80
+ "shell_command=$7",
81
+ "setsid_command=$8",
82
+ "sleep_command=$9",
83
+ "shift 9",
84
+ "watch_name=$1",
85
+ "watch_script=$2",
86
+ "cleanup_name=$3",
87
+ "cleanup_script=$4",
88
+ "shift 4",
89
+ "process_start() {",
90
+ " process_stat=",
91
+ ' IFS= read -r process_stat < "/proc/$1/stat" || return 1',
92
+ " process_tail=${process_stat##*) }",
93
+ " set -- $process_tail",
94
+ ' case "$1" in Z|X|x) return 1 ;; esac',
95
+ ' printf "%s" "${20}"',
96
+ "}",
97
+ "matches_parent() {",
98
+ " parent_stat=",
99
+ ' IFS= read -r parent_stat < "/proc/$expected_parent/stat" || return 1',
100
+ " parent_tail=${parent_stat##*) }",
101
+ " set -- $parent_tail",
102
+ ' case "$1" in Z|X|x) return 1 ;; esac',
103
+ ' [ "${20}" = "$expected_parent_start" ]',
104
+ "}",
105
+ "terminate() {",
106
+ ' trap "" CONT TERM INT HUP',
107
+ " while :; do",
108
+ ' "$setsid_command" "$shell_command" -c "$cleanup_script" "$cleanup_name" "$runtime" "$identity" "$$" ' +
109
+ '"$stat_command" "$rm_command" "$sleep_command" </dev/null >/dev/null 2>&1 &',
110
+ " cleanup=$!",
111
+ ' wait "$cleanup" 2>/dev/null',
112
+ ' "$sleep_command" 0.025',
113
+ " done",
114
+ "}",
115
+ "continue_or_terminate() {",
116
+ " remaining=40",
117
+ ' while matches_parent && [ "$remaining" -gt 0 ]; do',
118
+ ' "$sleep_command" 0.025',
119
+ " remaining=$((remaining - 1))",
120
+ " done",
121
+ " if ! matches_parent; then terminate; fi",
122
+ "}",
123
+ "trap continue_or_terminate CONT",
124
+ "trap terminate TERM INT HUP",
125
+ "supervisor_start=$(process_start \"$$\") || terminate",
126
+ '( unset GTKX_PROCESS_GUARD; exec "$setsid_command" "$shell_command" -c "$watch_script" "$watch_name" "$runtime" ' +
127
+ '"$identity" "$$" "$supervisor_start" "$expected_parent" "$expected_parent_start" "$stat_command" ' +
128
+ '"$rm_command" "$sleep_command" ) </dev/null >/dev/null 2>&1 &',
129
+ "child=",
130
+ "if ! matches_parent; then terminate; fi",
131
+ '"$@" &',
132
+ "child=$!",
133
+ "while :; do",
134
+ ' wait "$child"',
135
+ " status=$?",
136
+ ' if kill -0 "$child" 2>/dev/null; then continue; fi',
137
+ ' exit "$status"',
138
+ "done",
139
+ ].join("\n");
140
+ const processIdentity = (pid) => {
141
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
142
+ const fields = stat.slice(stat.lastIndexOf(") ") + 2).split(" ", 20);
143
+ const state = fields[0];
144
+ const startTime = fields[19];
145
+ if (startTime === undefined || state === undefined || ["Z", "X", "x"].includes(state)) {
146
+ throw new Error(`Failed to identify process ${String(pid)}`);
147
+ }
148
+ return { pid, startTime };
149
+ };
14
150
  const writeGuardCommand = (child, operation, job) => {
15
151
  child.stdin?.write(`${operation}${JSON.stringify(job)}\n`);
16
152
  };
@@ -21,11 +157,31 @@ const releaseGuard = (child) => {
21
157
  stdin.unref();
22
158
  }
23
159
  };
24
- const startGuard = () => {
160
+ const isSameProcessWatch = (left, right) => left.owner.pid === right.owner.pid &&
161
+ left.owner.startTime === right.owner.startTime &&
162
+ left.target.pid === right.target.pid &&
163
+ left.target.startTime === right.target.startTime;
164
+ const configureProcessWatch = (watch) => {
165
+ if (watch === undefined) {
166
+ return;
167
+ }
168
+ if (guard.watch !== undefined && !isSameProcessWatch(guard.watch, watch)) {
169
+ throw new Error("The process guard is already watching another owner");
170
+ }
171
+ if (guard.child !== undefined && guard.watch === undefined) {
172
+ throw new Error("The process guard was started before its owner was registered");
173
+ }
174
+ guard.watch = watch;
175
+ };
176
+ const guardArguments = () => guard.watch === undefined
177
+ ? [GUARD_PATH, RUN_PREFIX]
178
+ : [GUARD_PATH, RUN_PREFIX, JSON.stringify(guard.watch)];
179
+ const startGuard = (watch) => {
180
+ configureProcessWatch(watch);
25
181
  if (guard.child) {
26
182
  return;
27
183
  }
28
- const child = spawn(process.execPath, [GUARD_PATH, RUN_PREFIX], {
184
+ const child = spawn(process.execPath, guardArguments(), {
29
185
  detached: true,
30
186
  stdio: ["pipe", "ignore", "ignore"],
31
187
  });
@@ -50,6 +206,17 @@ const startGuard = () => {
50
206
  }
51
207
  releaseGuard(child);
52
208
  };
209
+ const watchParentProcess = () => {
210
+ const parentId = process.ppid;
211
+ const watch = {
212
+ owner: processIdentity(parentId),
213
+ target: processIdentity(process.pid),
214
+ };
215
+ if (process.ppid !== parentId) {
216
+ throw new Error("The parent process exited while its guard was starting");
217
+ }
218
+ startGuard(watch);
219
+ };
53
220
  const registerJob = (job) => {
54
221
  guard.jobs.set(job.marker, job);
55
222
  if (guard.child) {
@@ -74,13 +241,21 @@ const rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] })
74
241
  removeCleanupDirectory(identity);
75
242
  }
76
243
  };
77
- function spawnWithParentDeathSignal(command, args, options = {}) {
244
+ const captureCleanupDirectories = (command, paths) => {
245
+ const identities = paths.map((path) => cleanupDirectoryIdentity(path));
246
+ if (identities.includes(undefined)) {
247
+ throw new Error(`Failed to identify a cleanup directory for ${command}`);
248
+ }
249
+ return identities.filter((identity) => identity !== undefined);
250
+ };
251
+ const spawnGuarded = (command, options, signal, launchArguments) => {
78
252
  const executable = resolveExecutable(command);
79
253
  const setpriv = resolveExecutable("setpriv");
80
254
  const jobValue = `${RUN_ID}/${randomBytes(4).toString("hex")}`;
81
255
  const marker = `${PROCESS_MARKER}=${jobValue}`;
256
+ const cleanupDirectories = captureCleanupDirectories(command, options.cleanupDirectories ?? []);
82
257
  startGuard();
83
- const child = spawn(setpriv, ["--pdeathsig", "SIGKILL", executable, ...args], {
258
+ const child = spawn(setpriv, launchArguments(executable, cleanupDirectories), {
84
259
  detached: true,
85
260
  stdio: options.stdio ?? "ignore",
86
261
  env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
@@ -88,26 +263,55 @@ function spawnWithParentDeathSignal(command, args, options = {}) {
88
263
  const processGroupId = child.pid;
89
264
  const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);
90
265
  if (group === undefined) {
91
- rollbackSpawn({ child, marker });
266
+ rollbackSpawn({ child, marker, cleanupDirectories });
92
267
  throw new Error(`Failed to identify process group for ${command}`);
93
268
  }
94
- const cleanupDirectories = (options.cleanupDirectories ?? []).map((path) => cleanupDirectoryIdentity(path));
95
- if (cleanupDirectories.includes(undefined)) {
96
- const captured = cleanupDirectories.filter((identity) => identity !== undefined);
97
- rollbackSpawn({ child, marker, processGroup: group, cleanupDirectories: captured });
98
- throw new Error(`Failed to identify a cleanup directory for ${command}`);
99
- }
100
- const job = {
101
- marker,
102
- processGroup: group,
103
- cleanupDirectories: cleanupDirectories.filter((identity) => identity !== undefined),
104
- };
269
+ const job = { marker, processGroup: group, cleanupDirectories, signal };
105
270
  registerJob(job);
106
271
  child.on("exit", () => {
107
272
  killMarkedProcesses(job.marker);
108
273
  unregisterJob(job);
109
274
  });
110
275
  return child;
276
+ };
277
+ function spawnWithParentDeathSignal(command, args, options = {}) {
278
+ return spawnGuarded(command, options, "SIGKILL", (executable) => ["--pdeathsig", "SIGKILL", executable, ...args]);
111
279
  }
112
- export { spawnWithParentDeathSignal };
280
+ const spawnWithParentDeathSupervisor = (command, args, options) => {
281
+ const shell = resolveExecutable("sh");
282
+ const stat = resolveExecutable("stat");
283
+ const rm = resolveExecutable("rm");
284
+ const setsid = resolveExecutable("setsid");
285
+ const sleep = resolveExecutable("sleep");
286
+ return spawnGuarded(command, { ...options, cleanupDirectories: [options.cleanupDirectory] }, "SIGCONT", (executable, cleanupDirectories) => {
287
+ const identity = cleanupDirectories[0];
288
+ if (identity === undefined) {
289
+ throw new Error(`Failed to identify a cleanup directory for ${command}`);
290
+ }
291
+ return [
292
+ "--pdeathsig",
293
+ "SIGCONT",
294
+ shell,
295
+ "-c",
296
+ SUPERVISOR_SCRIPT,
297
+ SUPERVISOR_NAME,
298
+ identity.path,
299
+ `${identity.device}:${identity.inode}:${identity.userId}`,
300
+ String(process.pid),
301
+ processIdentity(process.pid).startTime,
302
+ stat,
303
+ rm,
304
+ shell,
305
+ setsid,
306
+ sleep,
307
+ SUPERVISOR_WATCH_NAME,
308
+ SUPERVISOR_WATCH_SCRIPT,
309
+ SUPERVISOR_CLEANUP_NAME,
310
+ SUPERVISOR_CLEANUP_SCRIPT,
311
+ executable,
312
+ ...args,
313
+ ];
314
+ });
315
+ };
316
+ export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor, watchParentProcess };
113
317
  //# sourceMappingURL=spawn-with-parent-death-signal.js.map