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

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.
@@ -2,5 +2,5 @@ export { exitCodeForSignal } from "./exit-code-for-signal.ts";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.ts";
3
3
  export { killProcessGroup, type ProcessGroupIdentity, processGroupIdentity, } 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, } 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,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,oBAAoB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EACH,0BAA0B,EAC1B,8BAA8B,GACjC,MAAM,qCAAqC,CAAC"}
@@ -2,5 +2,5 @@ export { exitCodeForSignal } from "./exit-code-for-signal.js";
2
2
  export { installGracefulShutdown } from "./install-graceful-shutdown.js";
3
3
  export { killProcessGroup, processGroupIdentity, } 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, } 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,EACH,gBAAgB,EAEhB,oBAAoB,GACvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EACH,0BAA0B,EAC1B,8BAA8B,GACjC,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 {\n spawnWithParentDeathSignal,\n spawnWithParentDeathSupervisor,\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"]}
@@ -23,7 +23,8 @@ const isGuardJob = (value) => isRecord(value) &&
23
23
  typeof value.marker === "string" &&
24
24
  isProcessGroupIdentity(value.processGroup) &&
25
25
  Array.isArray(value.cleanupDirectories) &&
26
- value.cleanupDirectories.every(isCleanupDirectoryIdentity);
26
+ value.cleanupDirectories.every(isCleanupDirectoryIdentity) &&
27
+ (value.signal === "SIGKILL" || value.signal === "SIGCONT");
27
28
  const applyCommand = (command) => {
28
29
  const operation = command[0];
29
30
  let value;
@@ -53,8 +54,10 @@ const receiveCommands = (chunk) => {
53
54
  };
54
55
  const killJobs = () => {
55
56
  for (const job of state.jobs.values()) {
56
- killProcessGroup(job.processGroup);
57
- killMarkedProcesses(job.marker);
57
+ killProcessGroup(job.processGroup, job.signal);
58
+ if (job.signal === "SIGKILL") {
59
+ killMarkedProcesses(job.marker);
60
+ }
58
61
  }
59
62
  };
60
63
  const removeCleanupDirectories = () => {
@@ -75,10 +78,10 @@ const sweep = () => {
75
78
  state.isSweeping = true;
76
79
  applyCommand(state.bufferedCommands);
77
80
  killJobs();
81
+ removeCleanupDirectories();
78
82
  if (GUARD_PREFIX.length > 0) {
79
83
  killMarkedProcessRun(GUARD_PREFIX);
80
84
  }
81
- removeCleanupDirectories();
82
85
  process.exit(0);
83
86
  };
84
87
  process.stdin.resume();
@@ -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 { 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 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 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 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 sweep = (): void => {\n if (state.isSweeping) {\n return;\n }\n\n state.isSweeping = true;\n applyCommand(state.bufferedCommands);\n killJobs();\n removeCleanupDirectories();\n\n if (GUARD_PREFIX.length > 0) {\n killMarkedProcessRun(GUARD_PREFIX);\n }\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"]}
@@ -4,6 +4,10 @@ type ParentDeathSpawnOptions = {
4
4
  env?: NodeJS.ProcessEnv;
5
5
  cleanupDirectories?: string[];
6
6
  };
7
+ type ParentDeathSupervisorOptions = Omit<ParentDeathSpawnOptions, "cleanupDirectories"> & {
8
+ cleanupDirectory: string;
9
+ };
7
10
  declare function spawnWithParentDeathSignal(command: string, args: string[], options?: ParentDeathSpawnOptions): ChildProcess;
8
- export { spawnWithParentDeathSignal };
11
+ declare const spawnWithParentDeathSupervisor: (command: string, args: string[], options: ParentDeathSupervisorOptions) => ChildProcess;
12
+ export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor };
9
13
  //# 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;AA0MF,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,YAkCF,CAAC;AAEF,OAAO,EAAE,0BAA0B,EAAE,8BAA8B,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,46 @@ 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_SCRIPT = [
17
+ "runtime=$1",
18
+ "identity=$2",
19
+ "expected_parent=$3",
20
+ "expected_parent_start=$4",
21
+ "stat_command=$5",
22
+ "rm_command=$6",
23
+ "shift 6",
24
+ "matches_directory() {",
25
+ ' [ "$("$stat_command" -c "%d:%i:%u" -- "$runtime" 2>/dev/null)" = "$identity" ]',
26
+ "}",
27
+ "matches_parent() {",
28
+ " parent_stat=",
29
+ ' IFS= read -r parent_stat < "/proc/$expected_parent/stat" || return 1',
30
+ " parent_tail=${parent_stat##*) }",
31
+ " set -- $parent_tail",
32
+ ' [ "${20}" = "$expected_parent_start" ]',
33
+ "}",
34
+ "terminate() {",
35
+ ' trap "" CONT TERM INT HUP',
36
+ ' if matches_directory; then "$rm_command" -rf -- "$runtime"; fi',
37
+ ' kill -KILL "-$$" 2>/dev/null',
38
+ " exit 1",
39
+ "}",
40
+ "trap terminate CONT TERM INT HUP",
41
+ "if ! matches_parent; then terminate; fi",
42
+ '"$@" &',
43
+ "child=$!",
44
+ 'wait "$child"',
45
+ "exit $?",
46
+ ].join("\n");
47
+ const processStartTime = (pid) => {
48
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
49
+ const start = stat.slice(stat.lastIndexOf(") ") + 2).split(" ", 20)[19];
50
+ if (start === undefined) {
51
+ throw new Error(`Failed to identify process ${String(pid)}`);
52
+ }
53
+ return start;
54
+ };
14
55
  const writeGuardCommand = (child, operation, job) => {
15
56
  child.stdin?.write(`${operation}${JSON.stringify(job)}\n`);
16
57
  };
@@ -74,13 +115,21 @@ const rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] })
74
115
  removeCleanupDirectory(identity);
75
116
  }
76
117
  };
77
- function spawnWithParentDeathSignal(command, args, options = {}) {
118
+ const captureCleanupDirectories = (command, paths) => {
119
+ const identities = paths.map((path) => cleanupDirectoryIdentity(path));
120
+ if (identities.includes(undefined)) {
121
+ throw new Error(`Failed to identify a cleanup directory for ${command}`);
122
+ }
123
+ return identities.filter((identity) => identity !== undefined);
124
+ };
125
+ const spawnGuarded = (command, options, signal, launchArguments) => {
78
126
  const executable = resolveExecutable(command);
79
127
  const setpriv = resolveExecutable("setpriv");
80
128
  const jobValue = `${RUN_ID}/${randomBytes(4).toString("hex")}`;
81
129
  const marker = `${PROCESS_MARKER}=${jobValue}`;
130
+ const cleanupDirectories = captureCleanupDirectories(command, options.cleanupDirectories ?? []);
82
131
  startGuard();
83
- const child = spawn(setpriv, ["--pdeathsig", "SIGKILL", executable, ...args], {
132
+ const child = spawn(setpriv, launchArguments(executable, cleanupDirectories), {
84
133
  detached: true,
85
134
  stdio: options.stdio ?? "ignore",
86
135
  env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
@@ -88,26 +137,46 @@ function spawnWithParentDeathSignal(command, args, options = {}) {
88
137
  const processGroupId = child.pid;
89
138
  const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);
90
139
  if (group === undefined) {
91
- rollbackSpawn({ child, marker });
140
+ rollbackSpawn({ child, marker, cleanupDirectories });
92
141
  throw new Error(`Failed to identify process group for ${command}`);
93
142
  }
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
- };
143
+ const job = { marker, processGroup: group, cleanupDirectories, signal };
105
144
  registerJob(job);
106
145
  child.on("exit", () => {
107
146
  killMarkedProcesses(job.marker);
108
147
  unregisterJob(job);
109
148
  });
110
149
  return child;
150
+ };
151
+ function spawnWithParentDeathSignal(command, args, options = {}) {
152
+ return spawnGuarded(command, options, "SIGKILL", (executable) => ["--pdeathsig", "SIGKILL", executable, ...args]);
111
153
  }
112
- export { spawnWithParentDeathSignal };
154
+ const spawnWithParentDeathSupervisor = (command, args, options) => {
155
+ const shell = resolveExecutable("sh");
156
+ const stat = resolveExecutable("stat");
157
+ const rm = resolveExecutable("rm");
158
+ return spawnGuarded(command, { ...options, cleanupDirectories: [options.cleanupDirectory] }, "SIGCONT", (executable, cleanupDirectories) => {
159
+ const identity = cleanupDirectories[0];
160
+ if (identity === undefined) {
161
+ throw new Error(`Failed to identify a cleanup directory for ${command}`);
162
+ }
163
+ return [
164
+ "--pdeathsig",
165
+ "SIGCONT",
166
+ shell,
167
+ "-c",
168
+ SUPERVISOR_SCRIPT,
169
+ SUPERVISOR_NAME,
170
+ identity.path,
171
+ `${identity.device}:${identity.inode}:${identity.userId}`,
172
+ String(process.pid),
173
+ processStartTime(process.pid),
174
+ stat,
175
+ rm,
176
+ executable,
177
+ ...args,
178
+ ];
179
+ });
180
+ };
181
+ export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor };
113
182
  //# 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 {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.ts\";\nimport { resolveExecutable } from \"./resolve-executable.ts\";\n\ntype ParentDeathSpawnOptions = {\n stdio?: StdioOptions;\n env?: NodeJS.ProcessEnv;\n cleanupDirectories?: string[];\n};\n\ntype GuardJob = {\n marker: string;\n processGroup: ProcessGroupIdentity;\n cleanupDirectories: CleanupDirectoryIdentity[];\n};\n\ntype GuardState = {\n child?: ChildProcess | undefined;\n jobs: Map<string, GuardJob>;\n};\n\ntype SpawnRollback = {\n child: ChildProcess;\n marker: string;\n processGroup?: ProcessGroupIdentity | undefined;\n cleanupDirectories?: CleanupDirectoryIdentity[] | 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 = { jobs: new Map() };\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 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 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 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\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 const marker = `${PROCESS_MARKER}=${jobValue}`;\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 const processGroupId = child.pid;\n const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);\n\n if (group === undefined) {\n rollbackSpawn({ child, marker });\n throw new Error(`Failed to identify process group for ${command}`);\n }\n\n const cleanupDirectories = (options.cleanupDirectories ?? []).map((path) => cleanupDirectoryIdentity(path));\n\n if (cleanupDirectories.includes(undefined)) {\n const captured = cleanupDirectories.filter(\n (identity): identity is CleanupDirectoryIdentity => identity !== undefined,\n );\n rollbackSpawn({ child, marker, processGroup: group, cleanupDirectories: captured });\n throw new Error(`Failed to identify a cleanup directory for ${command}`);\n }\n\n const job: GuardJob = {\n marker,\n processGroup: group,\n cleanupDirectories: cleanupDirectories.filter((identity) => identity !== undefined),\n };\n registerJob(job);\n\n child.on(\"exit\", () => {\n killMarkedProcesses(job.marker);\n unregisterJob(job);\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 { readFileSync } from \"node:fs\";\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 {\n type CleanupDirectoryIdentity,\n cleanupDirectoryIdentity,\n killProcessGroup,\n type ProcessGroupIdentity,\n processGroupIdentity,\n removeCleanupDirectory,\n} from \"./kill-process-group.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 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};\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_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 \"shift 6\",\n \"matches_directory() {\",\n ' [ \"$(\"$stat_command\" -c \"%d:%i:%u\" -- \"$runtime\" 2>/dev/null)\" = \"$identity\" ]',\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 ' [ \"${20}\" = \"$expected_parent_start\" ]',\n \"}\",\n \"terminate() {\",\n ' trap \"\" CONT TERM INT HUP',\n ' if matches_directory; then \"$rm_command\" -rf -- \"$runtime\"; fi',\n ' kill -KILL \"-$$\" 2>/dev/null',\n \" exit 1\",\n \"}\",\n \"trap terminate CONT TERM INT HUP\",\n \"if ! matches_parent; then terminate; fi\",\n '\"$@\" &',\n \"child=$!\",\n 'wait \"$child\"',\n \"exit $?\",\n].join(\"\\n\");\n\nconst processStartTime = (pid: number): string => {\n const stat = readFileSync(`/proc/${String(pid)}/stat`, \"utf8\");\n const start = stat.slice(stat.lastIndexOf(\") \") + 2).split(\" \", 20)[19];\n\n if (start === undefined) {\n throw new Error(`Failed to identify process ${String(pid)}`);\n }\n\n return start;\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 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 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 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 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 processGroupId = child.pid;\n const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);\n\n if (group === undefined) {\n rollbackSpawn({ child, marker, cleanupDirectories });\n throw new Error(`Failed to identify process group for ${command}`);\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\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 processStartTime(process.pid),\n stat,\n rm,\n executable,\n ...args,\n ];\n },\n );\n};\n\nexport { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor };\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/utils",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.3",
4
4
  "description": "Shared utilities for the GTK toolchain: logging, errors, strings.",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -6,4 +6,7 @@ export {
6
6
  processGroupIdentity,
7
7
  } from "./kill-process-group.ts";
8
8
  export { resolveExecutable, tryResolveExecutable } from "./resolve-executable.ts";
9
- export { spawnWithParentDeathSignal } from "./spawn-with-parent-death-signal.ts";
9
+ export {
10
+ spawnWithParentDeathSignal,
11
+ spawnWithParentDeathSupervisor,
12
+ } from "./spawn-with-parent-death-signal.ts";
@@ -41,13 +41,13 @@ const isCurrentProcessGroup = (identity: ProcessGroupIdentity): boolean => {
41
41
  return current?.leaderStartTime === identity.leaderStartTime;
42
42
  };
43
43
 
44
- const killProcessGroup = (identity: ProcessGroupIdentity): void => {
44
+ const killProcessGroup = (identity: ProcessGroupIdentity, signal: NodeJS.Signals = "SIGKILL"): void => {
45
45
  if (!isCurrentProcessGroup(identity)) {
46
46
  return;
47
47
  }
48
48
 
49
49
  try {
50
- process.kill(-identity.processGroupId, "SIGKILL");
50
+ process.kill(-identity.processGroupId, signal);
51
51
  } catch {
52
52
  return;
53
53
  }
@@ -12,6 +12,7 @@ type GuardJob = {
12
12
  marker: string;
13
13
  processGroup: ProcessGroupIdentity;
14
14
  cleanupDirectories: CleanupDirectoryIdentity[];
15
+ signal: NodeJS.Signals;
15
16
  };
16
17
 
17
18
  const state: { bufferedCommands: string; isSweeping: boolean; jobs: Map<string, GuardJob> } = {
@@ -43,7 +44,8 @@ const isGuardJob = (value: unknown): value is GuardJob =>
43
44
  typeof value.marker === "string" &&
44
45
  isProcessGroupIdentity(value.processGroup) &&
45
46
  Array.isArray(value.cleanupDirectories) &&
46
- value.cleanupDirectories.every(isCleanupDirectoryIdentity);
47
+ value.cleanupDirectories.every(isCleanupDirectoryIdentity) &&
48
+ (value.signal === "SIGKILL" || value.signal === "SIGCONT");
47
49
 
48
50
  const applyCommand = (command: string): void => {
49
51
  const operation = command[0];
@@ -78,8 +80,11 @@ const receiveCommands = (chunk: Buffer | string): void => {
78
80
 
79
81
  const killJobs = (): void => {
80
82
  for (const job of state.jobs.values()) {
81
- killProcessGroup(job.processGroup);
82
- killMarkedProcesses(job.marker);
83
+ killProcessGroup(job.processGroup, job.signal);
84
+
85
+ if (job.signal === "SIGKILL") {
86
+ killMarkedProcesses(job.marker);
87
+ }
83
88
  }
84
89
  };
85
90
 
@@ -105,12 +110,11 @@ const sweep = (): void => {
105
110
  state.isSweeping = true;
106
111
  applyCommand(state.bufferedCommands);
107
112
  killJobs();
113
+ removeCleanupDirectories();
108
114
 
109
115
  if (GUARD_PREFIX.length > 0) {
110
116
  killMarkedProcessRun(GUARD_PREFIX);
111
117
  }
112
-
113
- removeCleanupDirectories();
114
118
  process.exit(0);
115
119
  };
116
120
 
@@ -1,5 +1,6 @@
1
1
  import { type ChildProcess, spawn, type StdioOptions } 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";
@@ -20,10 +21,19 @@ type ParentDeathSpawnOptions = {
20
21
  cleanupDirectories?: string[];
21
22
  };
22
23
 
24
+ type ParentDeathSupervisorOptions = Omit<ParentDeathSpawnOptions, "cleanupDirectories"> & {
25
+ cleanupDirectory: string;
26
+ };
27
+
28
+ type GuardSignal = "SIGKILL" | "SIGCONT";
29
+
30
+ type LaunchArguments = (executable: string, cleanupDirectories: CleanupDirectoryIdentity[]) => string[];
31
+
23
32
  type GuardJob = {
24
33
  marker: string;
25
34
  processGroup: ProcessGroupIdentity;
26
35
  cleanupDirectories: CleanupDirectoryIdentity[];
36
+ signal: GuardSignal;
27
37
  };
28
38
 
29
39
  type GuardState = {
@@ -34,8 +44,8 @@ type GuardState = {
34
44
  type SpawnRollback = {
35
45
  child: ChildProcess;
36
46
  marker: string;
37
- processGroup?: ProcessGroupIdentity | undefined;
38
- cleanupDirectories?: CleanupDirectoryIdentity[] | undefined;
47
+ processGroup?: ProcessGroupIdentity;
48
+ cleanupDirectories?: CleanupDirectoryIdentity[];
39
49
  };
40
50
 
41
51
  const MODULE_PATH = fileURLToPath(import.meta.url);
@@ -43,6 +53,49 @@ const GUARD_PATH = join(dirname(MODULE_PATH), `process-guard${extname(MODULE_PAT
43
53
  const RUN_ID = randomBytes(8).toString("hex");
44
54
  const RUN_PREFIX = `${PROCESS_MARKER}=${RUN_ID}/`;
45
55
  const guard: GuardState = { jobs: new Map() };
56
+ const SUPERVISOR_NAME = "gtkx-process-supervisor";
57
+ const SUPERVISOR_SCRIPT = [
58
+ "runtime=$1",
59
+ "identity=$2",
60
+ "expected_parent=$3",
61
+ "expected_parent_start=$4",
62
+ "stat_command=$5",
63
+ "rm_command=$6",
64
+ "shift 6",
65
+ "matches_directory() {",
66
+ ' [ "$("$stat_command" -c "%d:%i:%u" -- "$runtime" 2>/dev/null)" = "$identity" ]',
67
+ "}",
68
+ "matches_parent() {",
69
+ " parent_stat=",
70
+ ' IFS= read -r parent_stat < "/proc/$expected_parent/stat" || return 1',
71
+ " parent_tail=${parent_stat##*) }",
72
+ " set -- $parent_tail",
73
+ ' [ "${20}" = "$expected_parent_start" ]',
74
+ "}",
75
+ "terminate() {",
76
+ ' trap "" CONT TERM INT HUP',
77
+ ' if matches_directory; then "$rm_command" -rf -- "$runtime"; fi',
78
+ ' kill -KILL "-$$" 2>/dev/null',
79
+ " exit 1",
80
+ "}",
81
+ "trap terminate CONT TERM INT HUP",
82
+ "if ! matches_parent; then terminate; fi",
83
+ '"$@" &',
84
+ "child=$!",
85
+ 'wait "$child"',
86
+ "exit $?",
87
+ ].join("\n");
88
+
89
+ const processStartTime = (pid: number): string => {
90
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
91
+ const start = stat.slice(stat.lastIndexOf(") ") + 2).split(" ", 20)[19];
92
+
93
+ if (start === undefined) {
94
+ throw new Error(`Failed to identify process ${String(pid)}`);
95
+ }
96
+
97
+ return start;
98
+ };
46
99
 
47
100
  const writeGuardCommand = (child: ChildProcess, operation: "+" | "-", job: GuardJob): void => {
48
101
  child.stdin?.write(`${operation}${JSON.stringify(job)}\n`);
@@ -124,18 +177,30 @@ const rollbackSpawn = ({ child, marker, processGroup, cleanupDirectories = [] }:
124
177
  }
125
178
  };
126
179
 
127
- function spawnWithParentDeathSignal(
180
+ const captureCleanupDirectories = (command: string, paths: string[]): CleanupDirectoryIdentity[] => {
181
+ const identities = paths.map((path) => cleanupDirectoryIdentity(path));
182
+
183
+ if (identities.includes(undefined)) {
184
+ throw new Error(`Failed to identify a cleanup directory for ${command}`);
185
+ }
186
+
187
+ return identities.filter((identity): identity is CleanupDirectoryIdentity => identity !== undefined);
188
+ };
189
+
190
+ const spawnGuarded = (
128
191
  command: string,
129
- args: string[],
130
- options: ParentDeathSpawnOptions = {},
131
- ): ChildProcess {
192
+ options: ParentDeathSpawnOptions,
193
+ signal: GuardSignal,
194
+ launchArguments: LaunchArguments,
195
+ ): ChildProcess => {
132
196
  const executable = resolveExecutable(command);
133
197
  const setpriv = resolveExecutable("setpriv");
134
198
  const jobValue = `${RUN_ID}/${randomBytes(4).toString("hex")}`;
135
199
  const marker = `${PROCESS_MARKER}=${jobValue}`;
200
+ const cleanupDirectories = captureCleanupDirectories(command, options.cleanupDirectories ?? []);
136
201
  startGuard();
137
202
 
138
- const child = spawn(setpriv, ["--pdeathsig", "SIGKILL", executable, ...args], {
203
+ const child = spawn(setpriv, launchArguments(executable, cleanupDirectories), {
139
204
  detached: true,
140
205
  stdio: options.stdio ?? "ignore",
141
206
  env: { ...(options.env ?? process.env), [PROCESS_MARKER]: jobValue },
@@ -145,25 +210,11 @@ function spawnWithParentDeathSignal(
145
210
  const group = processGroupId === undefined ? undefined : processGroupIdentity(processGroupId);
146
211
 
147
212
  if (group === undefined) {
148
- rollbackSpawn({ child, marker });
213
+ rollbackSpawn({ child, marker, cleanupDirectories });
149
214
  throw new Error(`Failed to identify process group for ${command}`);
150
215
  }
151
216
 
152
- const cleanupDirectories = (options.cleanupDirectories ?? []).map((path) => cleanupDirectoryIdentity(path));
153
-
154
- if (cleanupDirectories.includes(undefined)) {
155
- const captured = cleanupDirectories.filter(
156
- (identity): identity is CleanupDirectoryIdentity => identity !== undefined,
157
- );
158
- rollbackSpawn({ child, marker, processGroup: group, cleanupDirectories: captured });
159
- throw new Error(`Failed to identify a cleanup directory for ${command}`);
160
- }
161
-
162
- const job: GuardJob = {
163
- marker,
164
- processGroup: group,
165
- cleanupDirectories: cleanupDirectories.filter((identity) => identity !== undefined),
166
- };
217
+ const job: GuardJob = { marker, processGroup: group, cleanupDirectories, signal };
167
218
  registerJob(job);
168
219
 
169
220
  child.on("exit", () => {
@@ -172,6 +223,59 @@ function spawnWithParentDeathSignal(
172
223
  });
173
224
 
174
225
  return child;
226
+ };
227
+
228
+ function spawnWithParentDeathSignal(
229
+ command: string,
230
+ args: string[],
231
+ options: ParentDeathSpawnOptions = {},
232
+ ): ChildProcess {
233
+ return spawnGuarded(
234
+ command,
235
+ options,
236
+ "SIGKILL",
237
+ (executable) => ["--pdeathsig", "SIGKILL", executable, ...args],
238
+ );
175
239
  }
176
240
 
177
- export { spawnWithParentDeathSignal };
241
+ const spawnWithParentDeathSupervisor = (
242
+ command: string,
243
+ args: string[],
244
+ options: ParentDeathSupervisorOptions,
245
+ ): ChildProcess => {
246
+ const shell = resolveExecutable("sh");
247
+ const stat = resolveExecutable("stat");
248
+ const rm = resolveExecutable("rm");
249
+
250
+ return spawnGuarded(
251
+ command,
252
+ { ...options, cleanupDirectories: [options.cleanupDirectory] },
253
+ "SIGCONT",
254
+ (executable, cleanupDirectories) => {
255
+ const identity = cleanupDirectories[0];
256
+
257
+ if (identity === undefined) {
258
+ throw new Error(`Failed to identify a cleanup directory for ${command}`);
259
+ }
260
+
261
+ return [
262
+ "--pdeathsig",
263
+ "SIGCONT",
264
+ shell,
265
+ "-c",
266
+ SUPERVISOR_SCRIPT,
267
+ SUPERVISOR_NAME,
268
+ identity.path,
269
+ `${identity.device}:${identity.inode}:${identity.userId}`,
270
+ String(process.pid),
271
+ processStartTime(process.pid),
272
+ stat,
273
+ rm,
274
+ executable,
275
+ ...args,
276
+ ];
277
+ },
278
+ );
279
+ };
280
+
281
+ export { spawnWithParentDeathSignal, spawnWithParentDeathSupervisor };